C - 结构体字节对齐问题

C语言结构体字节对齐原理与实践
本文详细探讨了C语言中结构体的字节对齐问题,包括字节对齐的原因、规则以及如何进行字节对齐。通过实例分析,解释了结构体内各成员变量的存储方式和填充字节的计算方法,强调了结构体长度必须满足对齐值的整数倍。同时,提到了VS2010中默认的对齐值以及如何用#pragma pack(n)指令改变它。

C - 结构体字节对齐问题

 

        关于C语言中的结构体字节对齐问题,在《C与指针》一书中提到,但是似乎没有说清楚,还是我理解不完全?所以,根据书上和网上资料,总结一些关于C语言中的结构体字节对齐的知识。这里的讨论和代码,都在VS2010下,GCC不太熟悉就不说了;-(

 

(1)什么是字节对齐[1]

对齐规则

        结构体中一个变量占用 n 个字节,则该变量的起始地址必须能够被 n 整除,即:存放起始地址 % n = 0,对于结构体而言,这个 n 取其成员中的数据类型占空间的值最大的那个。(这里n可以称为对齐值)

        补充:结构体的长度必须为结构体的实际对齐值(自身对齐值和编译器默认对齐值中最小的一个)的整数倍,不够就补空字节。

 

(2)为什么要字节对齐[1]

        内存空间是按照字节来划分的,从理论上说对内存空间的访问可以从任何地址开始,但是在实际上不同架构的 CPU 为了提高访问内存的速度,就规定了对于某些类型的数据只能从特定的起始位置开始访问。这样就决定了各种数据类型只能按照相应的规则在内存空间中存放,而不能一个接一个的顺序排列。

        举个例子,比如有些平台访问内存地址都从偶数地址开始,对于一个 int 型(假设 32 位系统),如果从偶数地址开始的地方存放,这样一个读周期就可以读出这个 int 数据,但是如果从奇数地址开始的地址存放,就需要两个读周期,并对两次读出的结果的高低字节进行拼凑才能得到这个int 数据,这样明显降低了读取的效率。

 

(3)如何进行字节对齐[1,2]

        参考[2],每个数据类型的变量(或称数据对象data object)都有一个对齐值(alignment-requirement),即sizeof(data type)。例如char类型变量的对齐值是1,int、long、float的对齐值是4,long long、double的对齐值则是8,单位都是字节。而结构体的对齐值则是成员变量中占用空间最大的那个变量的大小。

        根据上述结论,“一个变量占用 n 个字节,则该变量的起始地址必须能够被 n 整除,即:存放起始地址 % n = 0”,那么char类型变量的地址必须被1整除,int、long、float的地址必须被4整除,而long long、double必须被8整除。

        由于结构体里成员变量的地址是按照顺序存储的,因此不能保证每个成员变量满足上述规则,所以有些成员变量的地址之前或之后需要填充一些字节

        至于要填充多少字节,跟每个变量实际的对齐值有关。确定实际的对齐值,有2条规则:

1、每个编译器有默认的对齐值,VS2010中默认是8,可以在代码中加入#pragma pack(n)指令,告诉编译器默认对齐值的大小,这里的n必须是1,2,4,8,16之一。(命令行编译中可以使用/Zp[n]选项)

2、实际的对齐值,取编译器默认的对齐值(即n的值)和数据对象的自身对齐值(即sizeof(data type))中最小的一个,即

min( n,sizeof( item ) )

        记住这2条规则,下面的示例代码,不用运行,就可以手动计算出结果了。

示例代码:

#include <stdio.h>
// #pragma pack(8)

struct A { 
	char a;
	long b;
};

struct B {
	char a;
	struct A b;
	long c;
};

struct C {
	char a;
	struct A b;
	double c; // Change “long” to “double” in struct B
}; 

struct D {
	char a;
	struct A b;
	double c;
	int d; // Add “int” in struct C
};

struct E {
	char a;
	int b;
	struct A c;
	double d;
};

// Error: C语言中不允许空的结构体,而C++中则允许,将.c后缀换成.cpp试试?
struct F {

};

void main(void)
{
	printf("A's size: %d\n", sizeof(struct A));
	printf("B's size: %d\n", sizeof(struct B));
	printf("C's size: %d\n", sizeof(struct C));
	printf("D's size: %d\n", sizeof(struct D));
	printf("E's size: %d\n", sizeof(struct E));
	printf("F's size: %d\n", sizeof(struct F));
}

分析如下:

        I. struct A,假设a的地址从0开始,则a占1个字节,顺序下来,b地址则是1,不满足对齐规则,在b的地址之前填充3个字节(取最小的对齐值4),b的地址现在是4(1+3),接着b占4个字节。所以A的大小是8个字节(1+3+4)。

等价代码,如下

struct A { 
	char a;
	char padding[3]; // 填充3个字节
	long b;
};


        II. struct B,假设a的地址从0开始,则a占1个字节,顺序下来,结构体b地址则是1,由于结构体b自身对齐值是4(取成员变量中最大的sizeof(item)),不满足对齐规则,所以在其地址之前填充3个字节(取对齐值4),b的地址是4(1+3),接着结构体b占8个字节(由I可知b的大小),顺序下来,c的地址是12(1+3+8),满足c的对齐规则,不用填充,接着就是c占用的4个字节。所以B的大小是16个字节(1+3+8+4)。

等价代码,如下

struct B {
	char a;
	char padding[3]; // 填充3个字节
	struct A b;
	long c;
};

        III. struct C,由II可知,a和b变量已经占了12个字节,顺序下来,c的地址是12,不满足对齐规则(地址12不能整除double的8个字节),在c地址之前填充4个字节,这时c的地址是16(1+3+8+4),满足对齐规则,下面就是c占用的8个字节。所以C的大小是24个字节(1+3+8+4+8)

等价代码,如下

struct C {
	char a;
	char padding1[3]; // 填充3个字节
	struct A b;
	char padding2[4]; // 填充4个字节
	double c;
};

        IV. struct D,由III可知,a、b和c变量已经占了24个字节,顺序下来,d的地址是24(1+3+8+4+8),似乎满足对齐规则,于是接着d占用的4个字节,这时D的大小是28(1+3+8+4+8+4)。但是不满足上面提到的“结构体的长度必须为结构体的实际对齐值(自身对齐值和编译器默认对齐值中最小的一个)的整数倍,不够就补空字节。”(28不能被对齐值8整除),所以d占用4个字节后,后面还要填充4个字节。所以D的大小是32个字节。(1+3+8+4+8+4+4)

等价代码,如下

struct D {
	char a;
	char padding1[3]; // 填充3个字节
	struct A b;
	char padding2[4]; // 填充4个字节
	double c;
	int d;
	char padding3[4]; // 填充4个字节
};

        V. struct E,假设a的地址从0开始,则a占1个字节,顺序下来,b地址则是1,不满足对齐规则,在b的地址之前填充3个字节(取最小的对齐值4),b的地址现在是4(1+3),接着b占4个字节。顺序下来,c的地址是8(1+3+4),c的对齐值是8,满足对齐规则,接着c占用的8个字节。顺序下来,d的地址是16(1+3+4+8),d的对齐值是8,满足对齐规则,接着d占用的8个字节。所以D的大小是24个字节。(1+3+4+8+8)

等价代码,如下

struct E {
	char a;
	char padding[3]; // 填充3个字节
	int b;
	struct A c;
	double d;
};

        VI. struct F,是空结构体,在C语言中不允许。在C++则允许,而且空结构体(或空类)的大小是1。

 

在头文件下面,加入#pragma pack(n)指令,即指定编译器的默认的对齐值。作为一个小练习,下面的表格中都应该填多少?

#pragma pack(n)

sizeof(struct A)

sizeof(struct B)

sizeof(struct C)

sizeof(struct D)

sizeof(struct E)

1

 

 

 

 

 

2

 

 

 

 

 

4

 

 

 

 

 

8

8

16

24

32

24

16

 

 

 

 

 

答案是

#pragma pack(n)

sizeof(struct A)

sizeof(struct B)

sizeof(struct C)

sizeof(struct D)

sizeof(struct E)

1

5

10

14

18

18

2

6

12

16

20

20

4

8

16

20

24

24

8

8

16

24

32

24

16

8

16

24

32

24

 

参考资料:

1、http://blog.youkuaiyun.com/bytxl/article/details/7523487

2、 http://msdn.microsoft.com/en-us/library/1d48zaa8(v=vs.80).aspx
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值