C语言结构体中的零长数组

C语言结构体中的零长数组

实例

struct A
{
    int len;
    int var[0];
};

struct B
{
    int len;
    int *var;
}

其中,结构体A使用了零长数组,结构体B用了指针。

为什么要用零长数组

我归纳了以下三点原因:

  • 节约空间
    在int和指针占4字节的机器上

    sizeof(A) // 4
    sizeof(B) // 8
    

    这是因为A中的var是数组名,B中的var是指针变量,编译器对数组名和指针变量的处理方式不一样。
    我们可以把这些变量名看作一个地址(事实上编译器就是这么看的),编译时,编译器从一个变量名和地址的对应表中读取地址。一个变量名代表一个不变的地址常量。
    数组名就是一个地址常量。虽然指针名也是一个地址常量,但是指针名对应的地址储存的是一个值,这个值是另一个地址,这后一个地址才是数组真正存储的地方。因为数组直接表示目标地址,而指针间接指向目标地址,所以编译器直接把地址记住就行了,不需要再腾出地方存了。

    数组名指针名都是某个地址常量,但是指针存储的地址可变,所以有指针变量。

  • 加快速度
    上面已经说到数组名和指针的区别了。
    现在我们来看看编译器对数组和指针的不同使用方式。
    比如

    数组:
    int a[10];
    printf("%d\n",a[5]);
    
    变量名和地址的对应表 -> a: 0x12345678(目标地址) -> 
    0x12345678 + 5 -> 读取地址 0x12345678 + 5 上的值 -> 输出
    
    指针:
    int *b;
    printf("%d\n",b[5]);
    
    变量名和地址的对应表 -> b: 0x87654321 -> 
    读取地址 0x87654321 上的值 -> 值0x11223344(目标地址) -> 
    0x11223344 + 5 -> 读取地址 0x11223344 + 5 上的值 -> 输出
    

    上述 0x.... 都是某地址

    比起数组,指针多了一步读取指针变量的值,虽然可能快不了多少,但是姑且算一个优点。
    至于为什么我们写代码时对数组和指针的使用方式一样,这就是编译器的功劳了,编译器让我们可以用同样的代码使用数组和指针,但是编译器在真正将它们变成机器语言时可不会把它们看作一样。

    插一嘴,GNU在C++中实现的变长数组使用了类似的方式。

    int i = 10;
    int a[i];
    
  • 分配和释放内存时更方便安全

    零长数组:
    struct A *p = (struct A *)malloc(sizeof(struct A) + len * sizeof(int));
    // 输入你要进行的操作
    free(p);
    
    指针:
    struct B *p = (struct B *)malloc(sizeof(struct B));
    p->var = (int *)malloc(len * sizeof(int));
    // 输入你要进行的操作
    free(p->var);
    free(p);
    

    相比零长数组,指针不论是分配还是释放都多了一步,并且如果改成

    free(p);
    free(p->var);
    

    就会出错,产生内存泄漏

    这是因为零长数组的内存是集中分配的,数组就挂在struct A的后面,而指针是分开分配的,不一定连续,你当然可以用下述方式做到同样的效果,但是前面两个优点同样没有。

    struct C
    {
        int len;
        int *var;
    };
    
    struct C *p = (struct C *)malloc(sizeof(struct C) + len * sizeof(int));
    p->var = p + sizeof(struct C);
    // 输入你要进行的操作
    // ! 不能使用 free(p->var);
    free(p);
    
posted @ 2022-10-27 11:46  Violeshnv  阅读(32)  评论(0编辑  收藏  举报