0长度的数组在ISO C和C++的规格说明书中是不允许的,但是由于gcc 预先支持C99的这种玩法,所以,“零长度数组”在gcc环境下是合法的。
先看下面两个例子。
pzeroLengthArray.c
#include <stdio.h> struct str { int len; char *s; }; struct foo { struct str *a; }; int main() { struct foo f = {0}; printf("sizeof(struct str) = %d\n", sizeof(struct str)); printf("before f.a->s.\n"); if(f.a->s) { printf("before printf f.a->s.\n"); printf(f.a->s); printf("before printf f.a->s.\n"); } return 0; }
zeroLengthArray.c
#include <stdio.h> struct str { int len; char s[0]; }; struct foo { struct str *a; }; int main() { struct foo f = {0}; printf("sizeof(struct str) = %d\n", sizeof(struct str)); printf("before f.a->s.\n"); if(f.a->s) { printf("before printf f.a->s.\n"); printf(f.a->s); printf("before printf f.a->s.\n"); } return 0; }
编译,运行如下
gcc -g -o ptest pzeroLengthArray.c gcc -g -o test zeroLengthArray.c [root@SPA tmp]# ./test sizeof(struct str) = 4 before f.a->s. before printf f.a->s. Segmentation fault (core dumped) [root@SPA tmp]# ./ptest sizeof(struct str) = 16 before f.a->s. Segmentation fault (core dumped)
从上面的运行结果可以看出,sizeof的结果以及发生段错误的位置,均不相同。
生成汇编代码,分析如下
gcc -S pzeroLengthArray.c gcc -S zeroLengthArray.c [root@SPA tmp]# diff pzeroLengthArray.s zeroLengthArray.s 1c1 < .file "pzeroLengthArray.c" --- > .file "zeroLengthArray.c" 21c21 < movl $16, %esi --- > movl $4, %esi 27,30d26 < movq -16(%rbp), %rax < movq 8(%rax), %rax < testq %rax, %rax < je .L2 34c30 < movq 8(%rax), %rdi --- > leaq 4(%rax), %rdi 39d34 < .L2:
可以看到有
对于char s[0]来说,汇编代码用了leaq指令,leaq 4(%rax), %rdi
对于char*s来说,汇编代码用了movq指令,movq 8(%rax), %rdi
lea全称load effective address,是把地址放进去,而mov则是把地址里的内容放进去。
从这里可以看到,访问成员数组名其实得到的是数组的相对地址,而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的)。
访问相对地址,程序不会crash,但是,访问一个非法的地址中的内容,程序就会crash。
第一,节省内存。从上面的例子中可以看出,零长度数组不占用内存空间,而指针却占用内存空间。
第二,方便内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第三,这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。
test.c
#include <stdlib.h> #include <stdio.h> #include <string.h> struct zerodemo{ int num; char zero[0]; }; struct ptrdemo{ int num; char *zero; }; #define LEN (sizeof(char)*100) int main(){ struct zerodemo *zd =(struct zerodemo *)malloc(sizeof(struct zerodemo) + LEN); zd->num = 10; memset(zd->zero,'a', LEN); struct ptrdemo *pd = (struct ptrdemo *)malloc(sizeof(struct ptrdemo)); pd->zero = (char *)malloc(LEN); pd->num = 10; memset(pd->zero, 'a', LEN); return 0; }
gdb调试如下
(gdb) p zd $1 = (struct zerodemo *) 0x601010 (gdb) p *zd $2 = {num = 10, zero = 0x601014 'a' <repeats 100 times>, "!"} (gdb) p zd->zero $3 = 0x601014 'a' <repeats 100 times>, "!" (gdb) p pd $4 = (struct ptrdemo *) 0x601080 (gdb) p *pd $5 = {num = 10, zero = 0x6010a0 'a' <repeats 100 times>} (gdb) p pd->zero $6 = 0x6010a0 'a' <repeats 100 times> (gdb) x /20b zd 0x601010: 10 0 0 0 97 97 97 97 0x601018: 97 97 97 97 97 97 97 97 0x601020: 97 97 97 97 (gdb) x /20b pd 0x601080: 10 0 0 0 0 0 0 0 0x601088: -96 16 96 0 0 0 0 0 0x601090: 0 0 0 0 (gdb) x /20b zd->zero 0x601014: 97 97 97 97 97 97 97 97 0x60101c: 97 97 97 97 97 97 97 97 0x601024: 97 97 97 97 (gdb) x /20b pd->zero 0x6010a0: 97 97 97 97 97 97 97 97 0x6010a8: 97 97 97 97 97 97 97 97 0x6010b0: 97 97 97 97