一、零长数组(另一篇文章参考这里)
在标准 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
}
运行结果截图如下: