高山遇虎好运气:c++ 结构体字节对齐

来源:百度文库 编辑:九乡新闻网 时间:2024/05/02 10:47:40
c++ 结构体字节对齐[转]
    结构体(struct)的sizeof值,并不是简单的将其中各元素所占字节相加,而是要考虑到存储空间的字节对齐问题。先看下面定义的两个结构体.
struct
{
char a;
short b;
char c;
}S1;struct
{
char a;
char b;
short c;
}S2;分别用程序测试得出sizeof(S1)=6 , sizeof(S2)=4可见,虽然两个结构体所含的元素相同,但因为其中存放的元素类型顺序不一样,所占字节也出现差异。这就是字节对齐原因。通过字节对齐,有助于加快计算机的取数速度,否则就得多花指令周期。字节对齐原则结构体默认的字节对齐一般满足三个准则:1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员自身大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。通过这三个原则,就不难理解上面两个struct的差异了.对于struct S1, 为了使short变量满足字节对其准则(2), 即其存储位置相对于结构体首地址的offset是自身大小(short占2个字节)的整数倍,必须在字节a后面填充一个字节以对齐;再由准则(3),为了 满足结构体总大小为short大小的整数倍,必须再在c后面填充一个字节。
对于struct S2, 却不必如上所述的填充字节,因为其直接顺序存储已经满足了对齐准则。如果将上面两个结构体中的short都改为int(占4个字节), 那么会怎么样呢? 程序得出sizeof(S1)=12, sizeof(S2)=8
利用上面的准则,也不难计算得出这样的结果。S1中在a后面填充3个字节、在c后面填充3个字节,这样一共12个字节;S2中在a、b顺序存储之后填充两个字节用以对其,这样一共就8个字节。当然,在某些时候也可以设置字节对齐方式。这就需要使用 #pragma pack 。
#pragma pack(push) //压栈保存
#pragma pack(1)// 设置1字节对齐
struct
{
char a;
short b;
char c;
}S1;
#pragma pack(pop) // 恢复先前设置如上所示,将对其方式设为1字节对齐,那么S1就不填充字节,sizeof为各元素所占字节之和即4。这一点在从外部2进制文件中读入struct大小的数据到struct中,是很有用的.另外,还有如下的一种方式:
· __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
· __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

因为我看C++对象模型的时候,遇到了几个内存布局都是有关于对齐的一些细节,故此对结构体对齐再做一份小结,有人说:结构体对齐这个东西是依赖于编译器的,因此不用去研究,真的嘛?

 

也许是,也许不是,要看你是做那个行业的了,如果你是做系统地层,网络通讯,嵌入式系统的,一个字节的节省,也许对你是很大的期望呢。虽然具体的对齐方式是因编译器而异,但是对齐的基本原理是不变的,那个原理也许能指导我们编写程序的时候按照某个原则去进行。

 

不过,既然你用到了C或者C++,就多数是和系统底层有缘之人了,你说是嘛?呵呵。

 

现在先说在windows x86 32位机子下的MS vs2005编译器(就是:cl.exe for x86)下的对齐规则:

 

比如以下的结构体定义:

 

struct A

{

    int i;

    double d;

    char c;

};

 

问sizeof(struct A)在vs2005的大小?

 

现在说一下cl.exe(就是微软vs2005的编译器进程)在默认情况下是怎么做的:

 

1、对齐量的确定:找到A中最大的基本类型成员的大小,在本例中是8(double的大小).

 

2、当定义一个结构体变量struct A aA; 的时候,aA的起始地址要被由1确定的对齐量整除,在这个例子里,aA的起始地址一定要能被8整除)

 

2、然后开始分配int i,4字节的空间;再分配double d;注意double 是8字节,所以要分配在被8整除的地方,因此int  i后面空了4字节填充;

 

3、然后,分配char c;1字节,这个时候struct A的大小是4(这是int i;的) + 4 (这是填充的) + 8(这是double d;的) + 1 (这是char c;的)= 17;

 

4、最后,要求结构体总的大小要能被由1确定的对齐量整除,在这里是说struct A的大小要能被8整除,所以还要加上7字节的填充字符,一共是24字节。

 

从这里可以看出,vs2005的编译器的结构体填充有这样的规则(默认情况下,这个默认情况可以通过工程属性上面的选项修改):

 

1、对齐量的确定:结构体中最大的数据成员的字节数

 

2、当定义一个结构体变量的时候,起始地址一定能被确定的对齐量整除;

 

2、分配每一个成员的时候,该成员相对于起始地址的偏移(offset)要能被该成员的大小整除;

 

3、结构体总的大小能被确定的对齐量整除;

 

 

 

扩充的字节叫做pad(填充字节)

 

 

下面看linux g++ 3.4.3编译器在x86 的32位机子下的默认规则,那相对简单一些:(相当于VS2005中将对齐情况改成4字节对齐)

 

对齐的方式默认是4字节,所以每个成员都按照4字节方式对齐即可。上面的结构体的大小就是:

 

4 (int i;的大小) + 8(double 的大小) + 1(char 的大小) + 3 (为了4字节填充而补充的字节) = 16字节;

 

至于gcc的结构体变量的首地址分配的特征一时也找不到,还望各位多多指教。谢谢。

 

最后,如果定义一个没有任何成员的结构体,struct A{}; 该大小是多少呢?是1字节。如果定义了两个结构体变量struct A a1, a2; 1字节的填充能够使a1和a2的地址区分开来!

 

还有其他的编译器和操作系统就不是我所能知的了。

 

那么,这些规则也许不尽相同,但是给了我们一个编程时候的注意点,就是:

 

定义结构体的时候,成员最好能从大到小来定义,那样能相对的省空间。(至少不会比别的顺序差,一般情况下哈。)

 

比如以上的结构体,如果能这样定义:

 

struct A

{

    double d;

    int i;

    char c;

};

 

那么,无论是windows下的vs2005,还是linux下的gcc,都是16字节。

 

对齐情况还可以通过各个编译器给出的特性在代码中改,不过我没用过,就没有发言权,各位又需要可以参考别的文章。

 

以上的过程和推论是我看别的文章和上机试验的结果,如有不对之处,请各位指教,谢谢。

 

 

再说一点,对于有嵌套的情况,vs2005下的对齐我也不大确定,不过我的分析如下:

 

struct A

{

    char c;

    double d;

    int i;

};

 

struct B

{

    struct  A a;

    char c;

};

 

这种情况下,分析B的大小;

 

1、对齐量的确定:包括A中的各成员比较的,递归下去,比较各个基本类型成员的大小,去最大值,为8(double的大小);

 

2、起始地址和上面的一样,要求B的变量的其实地址能被8整除;

 

3、关于B中struct A a;的偏移和大小和将a单独作为结构体变量的时候一样(那样方便赋值运算)

 

剩下的和上面的规则相同;

 

所以:B的大小就是24(struct A)+ 1(char) +7(pad) = 32字节;