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);