08 July 2017

参考

陈皓:C 语言结构体里的成员数组和指针

这种玩法英文叫:Flexible Array,中文翻译叫:柔性数组

零长数组

第一个程序

 1: struct str {
 2:     int len;
 3:     char s[0];
 4: };
 5: 
 6: struct foo {
 7:     struct str *a;
 8: };
 9: 
10: int main(int argc, char** argv) {
11:     struct foo f = {0};
12:     if (f.a->s) {
13:         printf(f.a->s);
14:     }
15:     return 0;
16: }
f.a             返回的是 a 的地址
f.a.s           返回的是 s 的地址
printf(f.a->s); 表示要将 s 里面值取出来

第二个程序

 1: struct str {
 2:     int len;
 3:     char *s;
 4: };
 5: 
 6: struct foo {
 7:     struct str *a;
 8: };
 9: 
10: int main(int argc, char** argv) {
11:     struct foo f = {0};
12:     if (f.a->s) {
13:         printf(f.a->s);
14:     }
15:     return 0;
16: }
f.a             返回的是 a 的地址
f.a.s           表示要将 s 里面值取出来
printf(f.a->s); 表示要将 s 里面值取出来

因为这里的是 s 是一个指针,上面的 s 是一个数组,访问成员数组名其实得到的是数组的相对地址,而访问成员指针其实是相对地址里的内容
所以,第一个程序会在 第 13 行 core,第二个程序会在 第 12 行 core

将第一个程序改成如下,是不会发生 core 的,因为这里的 printf 不再对 s 取值,而是打印 s 的地址

struct str {
    int len;
    char s[0];
};

struct foo {
    struct str *a;
};

int main(int argc, char** argv) {
    struct foo f = {0};
    if (f.a->s) {
        printf("%p", f.a->s);   // 0x4
    }
    return 0;
}

零长数组的好处

struct line1 {
   int length;
   char *contents;
};

struct line1 *l = (struct line1 *) malloc (sizeof(struct line1));
l->content = (char *) malloc (sizeof(char) * 10);


struct line2 {
   int length;
   char contents[0];
};

struct line2 *l = (struct line2 *) malloc (sizeof(struct line2) + 10);

第一个意义是,方便内存释放。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用 free 可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次 free 就可以把所有的内存也给释放掉。(读到这里,你一定会觉得 C++的封闭中的析构函数会让这事容易和干净很多)

第二个原因是,这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)

另外,malloc 也会节省一个指针的大小