现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

结构体中的指针与零长度数组

2016-06-15 22:57 工业·编程 ⁄ 共 3275字 ⁄ 字号 暂无评论

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 

给我留言

留言无头像?