C的0长数组

 

一、零长数组(另一篇文章参考这里)

    在标准 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( " \n p=&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( " \n sizeof(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 }  

    运行结果截图如下:

C的0长数组_第1张图片

 

你可能感兴趣的:(c,struct,socket,Arrays,buffer,编译器)