有时候我们会见到结构体中使用长度为0的数组,如下,可以理解,使用来表示边长的内存空间。下图时linux libfdt中例子。在《c高级编程 基于模块化设计思想的C语言开发》一书中也有讲到这一节。
当然都是用来表示变长的内存的。
可以先下一个结论:这个data[0]是表示这个变量后紧挨着的内存区域,且不占内存空间,准确来说不占栈空间,但是栈用代码段空间。
看测试代码:
#include <unistd.h>
#include <stdio.h>
struct Test{
int len;
char data[0];
};
void main()
{
int a = 12345678;
struct Test b;
b.len = 4;
printf("sizeof(struct Test):%d\n", sizeof(struct Test));
printf("sizeof(b):%d\n", sizeof(b));
printf("a:%d\n", a);
printf("b.data:%p,&b.data:%p, *(int*)b.data:%d\n", b.data, &(b.data), *(int*)(b.data));
}
结果:
可以看到Test 结构体的长度还是4,为一个int的大小,data不占栈空间。这个怎么理解呢?比如你定义一个数组
void main()
{
` char a[10];
}
这个a代表堆栈中的一个内存空间,你打印a的值为这个这个内存的首地址。但是这个a却是不占堆栈空间的,它是被编译进代码段的,我们是通过代码计算直接访问这个内存空间的,a不占栈空间。
我们看测试代码的反汇编代码
00010440 <main>:
10440: e92d4800 push {fp, lr}
10444: e28db004 add fp, sp, #4
10448: e24dd008 sub sp, sp, #8
1044c: e306314e movw r3, #24910 ; 0x614e
10450: e34030bc movt r3, #188 ; 0xbc
10454: e50b3008 str r3, [fp, #-8]
10458: e3a03004 mov r3, #4
1045c: e50b300c str r3, [fp, #-12]
10460: e3a01004 mov r1, #4
10464: e3000538 movw r0, #1336 ; 0x538
10468: e3400001 movt r0, #1
1046c: ebffff9e bl 102ec <printf@plt>
10470: e3a01004 mov r1, #4
10474: e3000550 movw r0, #1360 ; 0x550
10478: e3400001 movt r0, #1
1047c: ebffff9a bl 102ec <printf@plt>
10480: e51b1008 ldr r1, [fp, #-8]
10484: e3000560 movw r0, #1376 ; 0x560
10488: e3400001 movt r0, #1
1048c: ebffff96 bl 102ec <printf@plt>
10490: e24b300c sub r3, fp, #12
10494: e2833004 add r3, r3, #4
10498: e5930000 ldr r0, [r3]
1049c: e24b300c sub r3, fp, #12
104a0: e2832004 add r2, r3, #4
104a4: e24b300c sub r3, fp, #12
104a8: e2831004 add r1, r3, #4
104ac: e1a03000 mov r3, r0
104b0: e3000568 movw r0, #1384 ; 0x568
104b4: e3400001 movt r0, #1
104b8: ebffff8b bl 102ec <printf@plt>
104bc: e320f000 nop {0}
104c0: e24bd004 sub sp, fp, #4
104c4: e8bd8800 pop {fp, pc}
栈里变量我们是用sp和fp间接寻址来访问的,c语言中变量的概念,在汇编中已经没有概念了,所以这个a[0]的a其实没有存储,只是一个内存空间的别名,变成了一句汇编指令:
104a4: e24b300c sub r3, fp, #12
104a8: e2831004 add r1, r3, #4
如果*是个指针呢?
void main()
{
char *p;
}
如果是指针,则它是内存中的一变量,需要占栈空间,其存储的值是一地址,即其指向另一块内存空间。
总结:数组和指针都是一种变量类型,不同长度的数组属于不通类型,这个使用typedef就可以显现,或者用数组类型强转时就会提示类型不一致。都是内存空间的一个别名,其符号本身不占内存空间,在代码段中用sp/fp间接寻址,但是其代表的内存是占内存空间的,而指针类型的长度固定为机器子长,arm中为4。而数组类型的长度是其[]中的长度相关的,如果为0,我们依然可以访问到这一块内存,在汇编中通过sp间接访问,由于其char [0]的长度为0,所以用sizeof读取时,长度不计算在内
数组是直接一块内存空间的别名,而指针是间接指向一块内存空间,所以数组a不占栈空间,而指针要栈用栈空间。
既然是表示变长的内存空间,为何不使用指针呢?
答案是指针没法表示该结构体变量后紧跟着的内存区域,且指针占栈空间,所以就没法表示紧跟着的空间。我们可以给指针赋值为紧跟着的空间,但是也只能是空出一个指针大小的空间了。
用处
格式化内存空间,比如帧结构,无需弹指针。比如TLV结构,Type-Length-Value结构,我们可以定义结构体:
struct {
char type;
int length;
char value[0];
}
使用时,根据length的长度,防卫value的内存区域,以防越界。