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

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

[[email protected] tmp]# ./test
sizeof(struct str) = 4
before f.a->s.
before printf f.a->s.
Segmentation fault (core dumped)
[[email protected] tmp]# ./ptest
sizeof(struct str) = 16
before f.a->s.
Segmentation fault (core dumped)

从上面的运行结果可以看出,sizeof的结果以及发生段错误的位置,均不相同。

从汇编分析原因

生成汇编代码,分析如下

gcc -S pzeroLengthArray.c

gcc -S zeroLengthArray.c

[[email protected] 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
时间: 2024-11-07 16:50:56

结构体中的指针与零长度数组的相关文章

file结构体中private_data指针的疑惑【转】

本文转载自:http://www.cnblogs.com/pengdonglin137/p/3328984.html hi all and barry, 最近在学习字符设备驱动,不太明白private_data在字符驱动中的作用,我们在 驱动中添加一个设备结构体,然后定义了这个结构体的全局指针变量,接着我们就能在 驱动程序中使用这个指针了.我看到很多驱动程序中都把结构体指针付给private_data, 然后对private_data操作. 为什么要使用private_data,难道仅仅是避免使

结构体中函数指针与typedef关键用途(函数指针)

// 结构体函数指针.  #include<stdio.h> //为了代码的移植考虑,一般使用typedef定义函数指针类 ,另一个好处是,减少代码的书写量.  typedef void (*shout)(char *name,int age); typedef struct {  //用指针来存储字符串     char *name;    int age ;    shout personinfo; }person; //类似于c++中类方法的实现,在这里,是为结构体中指针函数提供实现.在

C/C++ 错误笔记-在给结构体中的指针赋值时,要注意该指针是否已指向内存空间

先来看下面的例子: #include <stdlib.h> #include <string.h> #include <stdio.h> #pragma warning(disable:4996) typedef struct _Student { char name[64]; int age; }Student; typedef struct _Teacher { char name[64]; int age; char *p1; char **p2; Student

结构体中二级指针的赋值和释放

背景说明:写项目时,想把数据按照结构体的格式存放,但是数据中有一个字符串数组,也就是二级指针,牵涉到开辟空间和释放的问题,可能是太基础了,查了很久每查到资料,后来自己写出来了.记录以下 心得:见指针就开辟空间,开辟完成后依次对应回收 代码: 1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 5 typedef struct data{ 6 int id; 7 int count; 8 c

c 结构体中存在指针,指针的不同赋值方法

#include<stdio.h>#include<stdlib.h>#include<string.h>struct parameter{ char *fd; int hit;};int main() { struct parameter*pptr = (struct parameter*)malloc(sizeof(struct parameter)); memset(pptr, 0, sizeof(struct parameter)); //pptr->fd

结构体中最后一个成员为[0]或[1]长度数组(柔性数组成员)的用法

结构体中最后一个成员为[0]长度数组的用法:这是个广泛使用的常见技巧,常用来构成缓冲区.比起指针,用空数组有这样的优势:(1).不需要初始化,数组名直接就是所在的偏移:(2).不占任何空间,指针需要占用int长度空间,空数组不占任何空间.“这个数组不占用任何内存”,意味着这样的结构节省空间:“该数组的内存地址就和它后面的元素地址相同”,意味着无需初始化,数组名就是后面元素的地址,直接就能当指针使用. 这样的写法最适合制作动态buffer,因为可以这样分配空间malloc(sizeof(struc

结构体中的数据对齐

c语言结构中需要注意的就是数据存储的对齐方式. 对齐的好处:对齐主要是为了方便数据的访问,提高计算机的处理速度,但同时它会浪费内存空间. CPU的优化规则大致是这样的:对于n字节的元素,它的首地址能被n整除,才能获得最好的性能. 对齐的使用原则: 1.一般的基本对齐原则是按着最大的基本类型的长度进行对齐.较小的元素可以组合起来填充一段内存,实现基本的对齐.前提是其满足条件2. 2.结构体中的元素也要满足一定的分布条件,就是元素的存储起始地址要满足能够整除该元素类型的长度. 3.在结构体中存在结构

读陈浩的《C语言结构体里的成员数组和指针》总结,零长度数组

原文链接:C语言结构体里的成员数组和指针 复制如下: 单看这文章的标题,你可能会觉得好像没什么意思.你先别下这个结论,相信这篇文章会对你理解C语言有帮助.这篇文章产生的背景是在微博上,看到@Laruence同学出了一个关于C语言的题,微博链接.微博截图如下.我觉得好多人对这段代码的理解还不够深入,所以写下了这篇文章. 为了方便你把代码copy过去编译和调试,我把代码列在下面: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h>

认真体会 结构体中的零长度数组

以前只是知道这个东西,可以解决一些问题,最近自己做一个字符串操作的东东,看了下redis的源码,做个小的总结. struct cl_str_s{ int free; int len; char buf[]; }; 代码的意思是,我们定义了一个结构体,它有这么三个属性,作用很明显不详细说了. 思考两个问题: 1:char buf[]能不能用char *buf代替? 2:如果我把 buf[] 做一个 char *的转换(方便通用printf方法或者其它),那么给定一个转换后变量的地址,我该怎么获取结