c 结构体中的变长数组

摘要:
使用起来非常方便,创建时,malloc一段结构体大小加上可变长数据长度的空间给它,可变长部分可按数组的方式访问,释放时,直接把整个结构体free掉就可以了。程序的运行结果如下:sizeoftag1=8sizeoftag2=12sizeoftag3=8sizeoftag4=9l_tag2=0xbffffad0,&l_tag2.c=0xbffffad8,l_tag2.c=l_tag3=0xbffffac8,l_tag3.c=0xbffffad0l_tag4=0xbffffabc,l_tag4.c=0xbffffac4从上面程序和运行结果可以看出:tag1本身包括两个位整数,所以占了8个字节的空间。tag2包括了两个位的整数,外加一个char*的指针,所以占了12个字节。tag4更加补充说明了这一点。讲了这么多,其实本质上涉及到的是一个C语言里面的数组和指针的区别问题。

在Linux系统里,/usr/include/linux/if_pppox.h里面有这样一个结构:

structpppoe_tag{

__u16tag_type;

__u16tag_len;

chartag_data[0];

}__attribute((packed));

最后一个成员为可变长的数组,对于TLV(Type-Length-Value)形式的结构,或者其他需要变长度的结构体,用这种方式定义最好。使用起来非常方便,创建时,malloc一段结构体大小加上可变长数据长度的空间给它,可变长部分可按数组的方式访问,释放时,直接把整个结构体free掉就可以了。例子如下:

structpppoe_tag*sample_tag;

__u16sample_tag_len= 10;

sample_tag= (structpppoe_tag*)malloc(sizeof(structpppoe_tag)+sizeof(char)*sample_tag_len);

sample_tag->tag_type= 0xffff;

sample_tag->tag_len=sample_tag_len;

sample_tag->tag_data[0]=....

...

释放时,

free(sample_tag)

是否可以用char *tag_data代替呢?其实它和char *tag_data是有很大的区别,为了说明这个问题,我写了以下的程序:

例:test_size.c

10structtag1

20{

30inta;

40intb;

50}__attribute((packed));

60

70structtag2

80{

90inta;

100intb;

110char*c;

120}__attribute((packed));

130

140structtag3

150{

160inta;

170intb;

180charc[0];

190}__attribute((packed));

200

210structtag4

220{

230inta;

240intb;

250charc[1];

260}__attribute((packed));

270

280intmain()

290{

300structtag2l_tag2;

310structtag3l_tag3;

320structtag4l_tag4;

330

340memset(&l_tag2,0,sizeof(structtag2));

350memset(&l_tag3,0,sizeof(structtag3));

360memset(&l_tag4,0,sizeof(structtag4));

370

380printf("size of tag1 = %d ",sizeof(structtag1));

390printf("size of tag2 = %d ",sizeof(structtag2));

400printf("size of tag3 = %d ",sizeof(structtag3));

410

420printf("l_tag2 = %p,&l_tag2.c = %p,l_tag2.c = %p ",&l_tag2,&l_tag2.c,l_tag2.c);

430printf("l_tag3 = %p,l_tag3.c = %p ",&l_tag3,l_tag3.c);

440printf("l_tag4 = %p,l_tag4.c = %p ",&l_tag4,l_tag4.c);

450exit(0);

460}

__attribute((packed))是为了强制不进行字节对齐,这样比较容易说明问题。

程序的运行结果如下:

sizeoftag1= 8

sizeoftag2= 12

sizeoftag3= 8

sizeoftag4= 9

l_tag2= 0xbffffad0,&l_tag2.c= 0xbffffad8,l_tag2.c= (nil)

l_tag3= 0xbffffac8,l_tag3.c= 0xbffffad0

l_tag4= 0xbffffabc,l_tag4.c= 0xbffffac4

从上面程序和运行结果可以看出:tag1本身包括两个位整数,所以占了8个字节的空间。tag2包括了两个位的整数,外加一个char*的指针,所以占了12个字节。tag3才是真正看出charc[0]和char*c的区别,charc[0]中的c并不是指针,是一个偏移量,这个偏移量指向的是a、b后面紧接着的空间,所以它其实并不占用任何空间。tag4更加补充说明了这一点。所以,上面的structpppoe_tag的最后一个成员如果用char*tag_data定义,除了会占用多个字节的指针变量外,用起来会比较不方便:方法一,创建时,可以首先为structpppoe_tag分配一块内存,再为tag_data分配内存,这样在释放时,要首先释放tag_data占用的内存,再释放pppoe_tag占用的内存;方法二,创建时,直接为structpppoe_tag分配一块structpppoe_tag大小加上tag_data的内存,从例一的行可以看出,tag_data的内容要进行初始化,要让tag_data指向strctpppoe_tag后面的内存。

structpppoe_tag{

__u16tag_type;

__u16tag_len;

char*tag_data;

}__attribute((packed));

structpppoe_tag*sample_tag;

__u16sample_tag_len= 10;

方法一:

sample_tag= (structpppoe_tag*)malloc(sizeof(structpppoe_tag));

sample_tag->tag_len=sample_tag_len;

sample_tag->tag_data=malloc(sizeof(char)*sample_tag_len);

sample_tag->tag_data[0]=...

释放时:

free(sample_tag->tag_data);

free(sample_tag);

方法二:

sample_tag= (structpppoe_tag*)malloc(sizeof(structpppoe_tag)+sizeof(char)*sample_tag_len);

sample_tag->tag_len=sample_tag_len;

sample_tag->tag_data= ((char*)sample_tag)+sizeof(structpppoe_tag);

sample_tag->tag_data[0]=...

释放时:

free(sample_tag);

所以无论使用那种方法,都没有chartag_data[0]这样的定义来得方便。

讲了这么多,其实本质上涉及到的是一个C语言里面的数组和指针的区别问题。chara[1]里面的a和char*b的b相同吗?《ProgrammingAbstractionsinC》(Roberts,E.S.,机械工业出版社,.6)页里面说:“arrisdefinedtobeidenticalto&arr[0]”。也就是说,chara[1]里面的a实际是一个常量,等于&a[0]。而char*b是有一个实实在在的指针变量b存在。所以,a=b是不允许的,而b=a是允许的。两种变量都支持下标式的访问,那么对于a[0]和b[0]本质上是否有区别?我们可以通过一个例子来说明。

例二:

10#include<stdio.h>

20#include<stdlib.h>

30

40intmain()

50{

60chara[10];

70char*b;

80

90a[2]=0xfe;

100b[2]=0xfe;

110exit(0);

120}

编译后,用objdump可以看到它的汇编:

080483f0 <main>:

80483f0:55push%ebp

80483f1:89e5mov%esp,%ebp

80483f3:83ec18sub$0x18,%esp

80483f6:c645f6femovb$0xfe,0xfffffff6(%ebp)

80483fa:8b45f0mov0xfffffff0(%ebp),%eax

80483fd:83c002add$0x2,%eax

8048400:c600femovb$0xfe,(%eax)

8048403:83c4f4add$0xfffffff4,%esp

8048406:6a00push$0x0

8048408:e8f3feffffcall8048300 <_init+0x68>

804840d:83c410add$0x10,%esp

8048410:c9leave

8048411:c3ret

8048412:8db426 00 00 00 00lea0x0(%esi,1),%esi

8048419:8dbc27 00 00 00 00lea0x0(%edi,1),%edi

可以看出,a[2]=xfe是直接寻址,直接将xfe写入&a[0]+2的地址,而b[2]=0xfe是间接寻址,先将b的内容(地址)拿出来,加,再xfe写入计算出来的地址。所以a[0]和b[0]本质上是不同的。但当数组作为参数时,和指针就没有区别了。

intdo1(chara[],intlen);

intdo2(char*a,intlen);

这两个函数中的a并无任何区别。都是实实在在存在的指针变量。

顺便再说一下,对于struct pppoe_tag的最后一个成员的定义是char tag_data[0],某些编译器不支持长度为0的数组的定义,在这种情况下,只能将它定义成char tag_data[1],使用方法相同。

免责声明:文章转载自《c 结构体中的变长数组》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【转】IOS开发—IOS 8 中设置applicationIconBadgeNumber和消息推送Jquery实现鼠标双击Table单元格变成文本框,输入内容并更新到数据库下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

[.net]数组

  在C语言中,数组是比较简单,也使用比较多的一种基础的数据结构。常用的有一维数组,二维数组等。但是在C#中,使用最多的是List,Dictionary等一些集合类,因为用他们来操作同类型的数据,比数组更加方便。当然,C#的数组Array也通过实现一些接口,提供了访问和操作数据的一些便捷方法。而在C语言中,都是比较不容易实现或者使用不方便。这也就是C#作为...

linux网络编程-socket(1)

 上面是对应的IpV4的地址结构: sin_len整个结构的大小 sin_family协议族,对应Tcp固定为AF_INET,除了tcp协议外还支持unix域协议等 sin_port socket通信的端口 sin_addr是一个无符号的32位的网络字节地址 上面的结构体仅仅支持IPv4地址协议,如果支持其他协议咱办了,后面引入了通用协议的地址协议 在...

Java 数组 可变长参数 实例

可以把类型相同但个数可变的参数传递给方法,方法中的参数声明如下: typeName...parameterName (类型名...参数名) 在方法声明中,指定类型后紧跟着省略号...,只能给方法指定一个可变长参数。 Java将可变长参数当成数组对待。可以将一个数组或可变的参数个数传递给可变长参数。当用可变的参数个数调用方法时,Java会创建一个数组并把参数...

GNU长选项命令行解析getopt_long()

原文链接 20 世纪 90 年代,UNIX 应用程序开始支持长选项,即一对短横线、一个描述性选项名称,还可以包含一个使用等号连接到选项的参数。GNU提供了getopt-long()和getopt-long-only()函数支持长选项的命令行解析,其中,后者的长选项字串是以一个短横线开始的,而非一对短横线。 getopt_long() 是同时支持长选项和短选...

【LeetCode-数组】最佳观光组合

题目描述 给定正整数数组 A,A[i] 表示第 i 个观光景点的评分,并且两个景点 i 和 j 之间的距离为 j - i。 一对景点(i < j)组成的观光组合的得分为(A[i] + A[j] + i - j):景点的评分之和减去它们两者之间的距离。 返回一对观光景点能取得的最高分。示例: 输入:[8,1,5,2,6] 输出:11 解释:i = 0,...

【转】SSE4.1指令集系列之一

转自:http://www.cnblogs.com/celerychen/archive/2013/04/02/2995586.html 本文要介绍的是SSE4.1指令集中的几条整数指令及其在视频编码中的应用。 1.单指令32字节差分绝对值求和指令MPSADBW 这条指令类似于SSE的PSADBW,但它实现的功能更强大。包括微软官方网站上对这条指令的说明都...