美文网首页
2019-05-20 c 基础回顾(2)

2019-05-20 c 基础回顾(2)

作者: 银TaMa | 来源:发表于2019-05-20 18:13 被阅读0次

    c 基础回顾

    1. const #define 区别

    推荐阅读

    1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
    (2)就起作用的方式而言: #define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
    (3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
    (4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。

    2.经典C语言面试题8:sizeof与strlen的区别

    1、sizeof是C/C++中的一个运算符,其作用是返回一个对象或者类型在内存中所占用的字节数。

    注意:sizeof后面如果是类型则必须加括号,如 sizeof(char);而如果是变量名则可以不加括号,如 sizeof a; 但是建议使用时 均加上括号。sizeof不能返回动态地被分配的数组的大小。

    2、strlen是C语言中的库函数,所在头文件为#include <string.h>其函数原型为unsigned int strlen(char *s); 其中s为指定的字符串。

    ​ 注意:strlen只能用char *作为参数,它求的是字符串的实际长度,方法是从开始到遇到第一个'\0'结束。

    sizeof是编译期就计算完成的,strlen是运行期计算的

    所以 sizeof的具有常量性,sizeof的计算发生在编译时刻,所以它可以被当作常量表达式使用

    当sizeof的对象是一个指针的时候呢?

    学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既
    然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。

    所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位)

    在64位系统中指针变量的sizeof结果为8。

    char* pc = "abc";
    int* pi;
    string* ps;
    char** ppc = &pc;
    void (*pf)();// 函数指针
    sizeof( pc ); // 结果为4
    sizeof( pi ); // 结果为4
    sizeof( ps ); // 结果为4
    sizeof( ppc ); // 结果为4
    sizeof( pf );// 结果为4
    

    数组的sizeof

    数组的sizeof值等于数组所占用的内存字节数,如:

    char a1[] = "abc";
    int a2[3];
    sizeof( a1 ); // 结果为4,字符 末尾还存在一个NULL终止符
    sizeof( a2 ); // 结果为3*4=12(依赖于int)
    

    一些朋友刚开始时把sizeof当作了求数组元素的个数,现在,你应该知道这是不对的,
    那么应该怎么求数组元素的个数呢Easy,通常有下面两种写法:

    int c1 = sizeof( a1 ) / sizeof( char ); // 总长度/单个元素的长度
    int c2 = sizeof( a1 ) / sizeof( a1[0] ); // 总长度/第一个元素的长度
    

    写到这里,提一问,下面的c3,c4值应该是多少呢

    void foo3(char a3[3])
    {
    int c3 = sizeof( a3 ); // c3 == 
    }
    void foo4(char a4[])
    {
    int c4 = sizeof( a4 ); // c4 == 
    }
    

    也许当你试图回答c4的值时已经意识到c3答错了,是的,c3!=3。这里函数参数a3已不
    再是数组类型,而是蜕变成指针,相当于char* a3,为什么仔细想想就不难明白,我
    们调用函数foo1时,程序会在栈上分配一个大小为3的数组吗不会!
    数组是“传址”的,调用者只需将实参的地址传递过去,所以a3自然为指针类型(char*)
    c3的值也就为4。

    当sizeof的对象是一个struct 类型变量的时候会出现什么问题呢?

    struct : 变量对齐

    经典C语言面试题4:字节对齐的作用

    一、字节对齐的作用?

    ​ 在现代计算机中,内存空间都是按照字节(byte)划分的。从理论上讲对任何类型的变量的访问可以从任何地址开始,但实际情况是,访问特定类型的变量的时候经常在特定的内存地址访问,这就需要各种类型的数据按照一定规则在空间上排列,而不是顺序地一个接一个地排放,这种所谓的规则就是字节对齐。这么长一段话的意思是说:字节对齐可以提升存取效率,也就是用空间换时间。

    二、为什么需要字节对齐?

    ​ 因为各个硬件平台对存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。

    三、如何对齐?

    1.先确定实际对齐单位,其由以下三个因素决定

    (1) CPU周期
    
    WIN  vs  qt  默认8字节对齐
    
    Linux 32位 默认4字节对齐,64位默认8字节对齐
    
    (2) 结构体最大成员(基本数据类型变量)
    
    (3) 预编译指令#pragma pack(n)手动设置     n--只能填1 2 4 8 16
    
    上面三者取最小的,就是实际对齐单位(这里的“实际对齐单位”是我为了方便区分随便取的概念)
    

    2.除结构体的第一个成员外,其他所有的成员的地址相对于结构体地址(即它首个成员的地址)的偏移量必须为实际对齐单位或自身大小的整数倍(取两者中小的那个)

    3.结构体的整体大小必须为实际对齐单位的整数倍。

    那如果结构体内部有个数组呢?那么就相对与有数组个数个数组类型的变量,

    比如

    typedef struct test{
        double d;
        char arr[12];
    }test;
    printf("%lu\n",sizeof(test));
    ==> 24
    

    那么关于class类中的sizeof计算

    1. 空类:1
      没有虚函数:sizeof(数据成员)的和
      有虚函数:sizeof(数据成员)的和+sizeof(V表指针)=4

      例:

      class no_virtual
      {
      public:
            void fun1() const{}
            int    fun2() const { return a; }
      private:
            int a;
      }
      
      class one_virtual
      {
      public:
            virtual void fun1() const{}
            int    fun2() const { return a; }
      private:
            int a;
      }
      
      class two_virtual
      {
      public:
            virtual void fun1() const{}
            virtual int    fun2() const { return a; }
      private:
            int a;
      }
      

      以上三个类中:
      no_virtual没有虚函数,sizeof(no_virtual)=4,类no_virtual的长度就是其成员变量整型a的长度;
      one_virtual有一个虚函数,sizeof(one_virtual)=8;
      two_virtual有两个虚函数,sizeof(two_virtual)=8; 有一个虚函数和两个虚函数的类的长度没有区别,其实它们的长度就是no_virtual的长度加一个void指针的长度,它反映出,如果有一个或多个虚函数,编译器在这个结构中插入一个指针( V P T R)。在one_virtual 和two_virtual之间没有区别。这是因为V P T R指向一个存放地址的表,只需要一个指针,因为所有虚函数地址都包含在这个表中。

    2. 若类中包含成员,则类对象的大小只包括其中非静态成员经过对齐所占的空间,对齐方式和结构体相同。如:

      class A
      {
      public:
      int b;
      float c;
      char d;
      };
      
      sizeof(A)是12.
      
      class A
      {
      public:
      static int a;
      int b;
      float c;
      char d;
      };
      sizeof(A)是12.
      class A
      {
      public:
      static int a;
      int b;
      float c;
      char d;
      int add(int x,int y)
      {
      return x+y;
      }
      };
      sizeof(A)也是12.
      

    3. 经典C语言面试题6:进程与线程的关系和区别

    关系和区别

    一个线程可以创建和撤销另一个线程;

    同一个进程中的多个线程之间可以并发执行。

    相对于进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他进程共享数据,但是拥有自己的栈空间,拥有独立的运行序列。

    区别主要有以下几点:

    1. 调度:进程是拥有资源的基本单位,线程是调度和分派的基本单位。
    2. 共享地址空间:进程拥有各自独立的地址空间、资源,所以共享复杂,需要用IPC(Inter-Process Communication,进程间通信),但是同步简单。而线程共享所属进程的资源,因此共享简单,但是同步复杂,需要用加锁等措施。
    3. 占用内存和cpu:进程占用内存多,切换复杂,cpu利用率低;而线程占用内存少,切换简单,cpu利用率高。
    4. 互相影响:进程之间不会互相影响;而一个线程挂掉会导致整个进程挂掉.

    关于僵尸进程

    产生原因:当子进程比父进程先运行结束,而父进程没有回收子进程时,子进程将会成为一个僵尸进程。如果父进程先退出了,那么子进程将会被init接管,从而就不会成为僵尸进程了,成为了孤儿进程。

    如何避免僵尸进程的产生?

    解决方法有以下几种:

    ​ 父进程通过wait或waitpid等待子进程结束,但是这会导致父进程挂起。
    ​ 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,这样在子进程结束后,父进程会收到该信号,就可以在handler中调用wait回收。
    ​ 若父进程不关心子进程何时结束,那么可以用 signal(SIGCHLD, SIG_IGN) 通知内核自己对于子进程的结束不感兴趣,这样子进程结束后内核会回收,并不会再给父进程发送信号。
    ​ 让僵尸进程变为“孤儿进程”(即杀死其父进程),过继给1号进程init,init始终会负责清理僵尸进程。

    相关文章

      网友评论

          本文标题:2019-05-20 c 基础回顾(2)

          本文链接:https://www.haomeiwen.com/subject/rqrlzqtx.html