C的0长数组以及__attribute__((packed))_
一、零长数组(另一篇文章参考这里)
在标准 C 或者 C++ 中由于不支持 0 长度的数组,所以 int array[0]; 这样的定义是非法的。不过有些编译器(如GCC)的扩展功能支持 0 长度的数组。
在 C 中,0 长度的数组的主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。由于其非标准性,在程序中尽量避免使用 0 长度的数组。作为替换,可以使用 C99 标准中的不完整数组来替换 0 长度的数组定义。如:
typedef struct _X {
int a;
char array[]; //注意,因为是不完整数组,因此不可以计算sizeof(X),无法编译通过
} X;
在GNU的gcc-4.4.0的官方指南中的5.14节Arrays of Length Zero一节中,它是如此写道:
struct line {
int length;
char contents[0];
};
//...ommit code here
struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length);
thisline->length = this_length;
这个用法主要用于变长Buffer,struct line的大小为4,结构体中的contents[0]不占用任何空间,甚至是一个指针的空间都不占(待会儿下面的示例代码会验证),contents在这儿只是表示一个常量指针,这个特性是用编译器来实现的,即在使用 thisline->contents的时候,这个指针就是表示分配内存地址中的某块buffer,比如malloc (sizeof (struct line) + this_length)返回的是0x8f00a40,thisline->contents指向的位置就是(0x8f00a40 + sizeof(struct line)),而这儿sizeof(struct line)仅仅是一个int的四字节。
对于这个用法,我们定义的结构体指针可以指向任意长度的内存buffer,这个技巧在变长buffer中使用起来相当方便。可能有朋友说,为什么不把最后的contents直接定义为一个指针呢?这儿的差别是这样的,如果定义为一个指针,它需要占用4Bytes,并且在申请好内存后必须人为赋地址才可以。如果使用这个用法,这个常量指针不占用空间,并且无需赋值。
但是,方便并不是绝对的,在释放分配的内存的时候,由于函数free会认为*thisline 只是指向一个4字节的指针,即只会释放length的空间,而对于后面占据大头的buffer却视而不见,这个就需要人为干预;而对于后面的声明指针的方式,则可以直接用Free(thisline->contents)的方式释放掉分配的内存。(这地方说的不明白,让我理解之后,感觉此话是错误的。)
如果将零长数组array换成指针*array来使用的话,指针必须重新分配一段内存之后才能使用,那么当想要用socket发送结构体指针的时候,并不会将指针array申请的内存发送过去,因为是不连续的,所以接受socket发送来的数据后,会发现该数据并不是自己想要的。
ASSERT:除非必要,不要轻易使用这个功能,GNU C下可以编译通过,所以你在使用vc++,那就不用尝试了,编译都无法通过。
C语言: 验证0长数组和__attribute__((packed))
01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <string.h>
04
05 #define offsetof(S,t) (size_t)&(((S *)0)->t) //求结构体中偏移量的宏,C++中存在此宏,C中需自己定义
06
07 struct zero_arry_t{
08 unsigned int i;
09 char arry[];
10 };
11
12 struct Test{
13 int len;
14 char content[0];
15 };
16
17 typedef struct _S1{
18 char a;
19 char b;
20 double c;
21 }S1;
22
23 typedef struct _S2{
24 char a;
25 char b;
26 double c;
27 }__attribute__((packed)) S2; //__attribute__((packed))的作用就是告诉编译器取消结构在编译过程中的优化对齐
28
29 typedef struct _Y
30 {
31 int a;
32 int b;
33 char c;
34 char content[0];
35 } Y;
36
37
38 int main()
39 {
40 //验证0长度数组
41 char c0 = 'a', c1 = 'b', c2='c', c3='d';
42 printf("c0=%c, c1=%c, c2=%c, c3=%c\n&c0=%p, &c1=%p, &c2=%p, &c3=%p\n", c0, c1, c2, c3, &c0, &c1, &c2, &c3);
43 struct Test t;
44 t.len = 0x01020304;
45 char *q = t.content;
46 printf("sizeof(t)=%u, sizeof(t.content)=%u\n", sizeof(t),sizeof(t.content)); //打印4 0, content本身不占空间
47 printf("&t=%p, &t.len=%p, t.content=%p, &t.content=%p\n", &t, &t.len, t.content, &t.content);//
48 strcpy(t.content, "123");
49 //发现 c0 c1 c2 c3的位置的内容被p->content所修改
50 printf("c0=%c, c1=%c, c2=%c, c3=%c\n&c0=%p, &c1=%p, &c2=%p, &c3=%p\n", c0, c1, c2, c3, &c0, &c1, &c2, &c3);
51
52 char buf[1024] = {0};
53 struct Test *p = (struct Test *)buf;
54 p->len = 0x01020304;
55 strcpy( p->content, "abcd");
56 printf("\np=&buf=%p, p->content=%p, p->content=%s\n", buf, p->content, p->content);
57 int k;
58 for(k=0; k<10; ++k) //注意观察这十个位置的值
59 printf("address %p: buf[%d]=%d\n", buf+k, k, buf[k]);
60
61 //关于offsetof宏,以及 __attribute__((packed))属性
62 printf("\nsizeof(S1)=%u, offsetof(S1,c)=%u\n", sizeof(S1),offsetof(S1,c));
63 //使用__attribute__后,结构体大小和成员的偏移量都发生变化
64 printf("sizeof(S2)=%u, offsetof(S2,c)=%u\n", sizeof(S2),offsetof(S2,c));
65 //对于有padding(补齐)的结构体Y,其sizeof(Y)和offsetof(Y, content)的大小不一致,参考这个帖子
66 printf("sizeof(Y)=%u, offsetof(Y, content)=%u, offsetof(Y, c)=%u\n", sizeof(Y), offsetof(Y, content), offsetof(Y, c));
67
68 getchar();
69 return 0;
70 }
运行结果截图如下:
二、__attribute__
1. __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法。这个功能是跟操作系统没关系,跟编译器有关,gcc编译器不是紧凑模式的,我在windows下,用vc的编译器也不是紧凑的,用tc的编译器就是紧凑的。例如:
在GCC下:struct my{ char ch; int a;} sizeof(int)=4;sizeof(my)=8;(非紧凑模式)
在GCC下:struct my{ char ch; int a;}__attrubte__ ((packed)) sizeof(int)=4;sizeof(my)=5
2. __attribute__关键字主要是用来在函数或数据声明中设置其属性。给函数赋给属性的主要目的在于让编译器进行优化。函数声明中的 __attribute__((noreturn)),就是告诉编译器这个函数不会返回给调用者,以便编译器在优化时去掉不必要的函数返回代码。
GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。
__attribute__书写特征是:__attribute__前后都有两个下划线,并且后面会紧跟一对括弧,括弧里面是相应的__attribute__参数。
__attribute__语法格式为:
__attribute__ ((attribute-list))
其位置约束:放于声明的尾部“;”之前。
函数属性(Function Attribute):函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。__attribute__机制也很容易同非GNU应用程序做到兼容之功效。
GNU CC需要使用 –Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。
packed属性:使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
举例:
/* __attribute__ ((packed)) 的位置约束是放于声明的尾部“;”之前 */
struct str_struct{
__u8 a;
__u8 b;
__u8 c;
__u16 d;
} __attribute__ ((packed));
/* 当用到typedef时,要特别注意__attribute__ ((packed))放置的位置,相当于:
* typedef struct str_stuct str;
* 而struct str_struct 就是上面的那个结构。
*/
typedef struct {
__u8 a;
__u8 b;
__u8 c;
__u16 d;
} __attribute__ ((packed)) str;
/* 在下面这个typedef结构中,__attribute__ ((packed))放在结构名str_temp之后,其作用是被忽略的,注意与结构str的区别。*/
typedef struct {
__u8 a;
__u8 b;
__u8 c;
__u16 d;
}str_temp __attribute__ ((packed)); //这样不起作用,还可能编译不通过