0长度的数组在ISO C和C++的规格说明书中是不允许的,但是由于gcc 预先支持C99的这种玩法,所以,“零长度数组”在gcc环境下是合法的。
先看下面两个例子。
pzeroLengthArray.c
[cpp] view plain copy
#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
[cpp] view plain copy
#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;
}
编译,运行如下
[python] view plain copy
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的结果以及发生段错误的位置,均不相同。
从汇编分析原因
生成汇编代码,分析如下
[python] view plain copy
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
[cpp] view plain copy
#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调试如下
[python] view plain copy
(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