详解4字节对齐

简介: 所谓的字节对齐,就是各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这个就是对齐。我们经常听说的对齐在N上,它的含义就是数据的存放起始地址%N==0。具体对齐规则会在下面的篇幅中介绍。

所谓的字节对齐,就是各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这个就是对齐。我们经常听说的对齐在N上,它的含义就是数据的存放起始地址%N==0。具体对齐规则会在下面的篇幅中介绍。首先还是让我们来看一下,为什么要进行字节对齐吧。
 
各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU,诸如SPARC在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构上必须编程必须保证字节对齐。而有些平台对于没有进行对齐的数据进行存取时会产生效率的下降。让我们来以x86为例看一下如果在不进行对齐的情况下,会带来什么样子的效率低下问题,看下面的数据结构声明:


view plaincopy to clipboardprint?
01.struct A {  
02.    char c;  
03.    int i;  
04.};  
05.struct A a;  
 


Code 13-1
 
假设变量a存放在内存中的起始地址为0x00,那么其成员变量c的起始地址为0x00,成员变量i的起始地址为0x01,变量a一共占用了5个字节。当CPU要对成员变量c进行访问时,只需要一个读周期即可。而如若要对成员变量i进行访问,那么情况就变得有点复杂了,首先CPU用了一个读周期,从0x00处读取了4个字节(注意由于是32位架构),然后将0x01-0x03的3个字节暂存,接着又花费了一个读周期读取了从0x04-0x07的4字节数据,将0x04这个字节与刚刚暂存的3个字节进行拼接从而读取到成员变量i的值。为了读取这个成员变量i,CPU花费了整整2个读周期。试想一下,如果数据成员i的起始地址被放在了0x04处,那么读取其所花费的周期就变成了1,显然引入字节对齐可以避免读取效率的下降,但这同时也浪费了3个字节的空间(0x01-0x03)。
 
有了上述的基本概念之后,让我们来看一下,编译器是按照什么样的原则进行对齐的。首先有3个重要的概念:自身对齐值,指定对齐值和有效对齐值。
 
自身对齐值:即数据类型的自身的对齐值。例如char型的数据,其自身对齐值为1字节;short型的数据,其自身对齐值为2字节;int,float,long类型,其自身对齐值为4字节;double类型,其自身对齐值为4字节;而struct和class类型的数据其自身对齐值为其成员变量中自身对齐值最大的那个值。
 
指定对齐值:#pragma pack (value)时指定的对齐值value
 
有效对齐值:上述两个对齐值中最小的那个。
 
我们一般说的对齐在N上,都是指有效对齐在N上。说了这么多,还是让我们先来看一些例子来加深对这些概念的理解吧。例:假设在x86机器上,假设编译器按默认4字节进行对齐

 


view plaincopy to clipboardprint?
01.struct A {  
02.    char c;  
03.    int i;  
04.    short s;  
05.};  
06.  
07.#pragma pack (2)   /* 指定按2字节对齐 */  
08.struct B {  
09.    char c;  
10.    short s;  
11.    int i;  
12.};  
13.#pragma pack ()    /* 恢复默认对齐 */  
 


Code 13-2
 
让我们来考虑一下sizeof(struct A)和sizeof(struct B)的结果各应该是什么。首先来看sizeof(struct A),假设A的起始地址为0x00,做这样的假设只是为了更方便理解,其实A始终被放在对齐边界上,这并不影响sizeof的结果,在接下来的例子中,我们也会继续沿用这个假设。言归正传,数据成员c的自身对齐值=1,指定对齐值=4(默认),所以其有效对齐值为1,因0x00%1==0,所以它被存放在0x00处;数据成员i的自身对齐值=4,指定对齐值=4,可得出其有效对齐值为4,因0x01%4 != 0,因此它应该被存放在0x04地址处,占用0x05,0x06,0x07共4个字节;接下来看数据成员s的自身对齐值=2,指定对齐值=4,得出有效对齐值为2,因0x08%2 == 0,因此它被存放在起始地址为0x08处,并占用2字节;最后再看数据结构A自身的对齐值=4(最大数据成员自身对齐值),指定对齐值=4,得有效对齐值为4,因0x0A%4 != 0,因此多占用0x0A和0x0B为结构体A所用(这一步的作用是基于结构体数组的出发,对于结构体或者类,要将它们补充成其有效对齐值的整数倍,这点请千万注意)。由此可见sizeof(struct A)的结果应该是=1+3(空闲空间)+4+2+2(结构体补充)=12字节。
 
接下来让我们考察sizeof(struct B)的结果。这里需要注意的是,在B被声明前,指定对齐值已经被设置为2个字节。数据成员c的有效对齐值为1,存放起址0x00,s的有效对齐值为2,存放起址0x02,i的有效对齐值也为2,存放起址为0x04,累加起来一共是8个字节,已经是数据结构B的有效对齐值2的整数倍了。因此sizeof(struct B)的结果8个字节。
 
看到这里,应该对字节对齐有了一定的了解了吧。接下来我们要看一个更加复杂的例子:假设在x86机器上,假设编译器按默认4字节进行对齐 

 


view plaincopy to clipboardprint?
01.#pragma pack(8)  
02.struct S1 {  
03.    char a;  
04.    long b;  
05.};  
06.  
07.struct S2 {  
08.    char c;  
09.    struct S1 d;  
10.    long long e;  
11.};  
12.#pragma pack()  
13.   
 


Code 13-3
 
运用上面所学到的知识,应该不难得出sizeof(struct S1)的值为8字节,其中a的有效对齐值为1,b的有效对齐值为4,结构S1的有效对齐值为4。现在让我们来看看sizeof(struct S2)的值会是多少呢?首先成员c的有效对齐值为1,S1的自身对齐值为成员的最大自身对齐值,即4字节,其指定对齐值为8,则其有效对齐值也为4,存放起址应该为0x04,并且占用8个字节(0x04+0x08=0x0C),其中0x01-0x03被用来填充。接下去数据成员e的有效对齐值为4,存放起址应该是0x0D % 8 == 0,占用8个字节(0x10)。最后考察S2本身的有效对齐值应该是4字节,而0x0D%8==0,就不需要填充了,因此sizeof(struct S2)=20。
 
在vc6工具中,我们可以选择[project]->[Settings]->[C/C++]->[Code Generation]->[Struct member alignment]来更改默认对齐字节数。
 
参考:http://www.sco.com/developers/devspecs/abi386-4.pdf

作者:Bonker
出处:http://www.cnblogs.com/Bonker
QQ:519841366
       
本页版权归作者和博客园所有,欢迎转载,但未经作者同意必须保留此段声明, 且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利
目录
相关文章
|
存储 编译器 C语言
关于内存对齐
关于内存对齐
133 0
|
编译器 Linux C++
C/C++中内存对齐的问题的讲解
C/C++中内存对齐的问题的讲解
173 0
|
存储 编译器
一文轻松理解内存对齐(2)
一文轻松理解内存对齐
195 0
|
机器学习/深度学习 存储 编译器
一文轻松理解内存对齐(1)
一文轻松理解内存对齐
528 0
|
存储 人工智能 编译器
结构体对齐问题1
C语言结构体对齐也是老生常谈的话题了。基本上是面试题的必考题。内容虽然很基础,但一不小心就会弄错。写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的变量总长度要大,这是怎么回事呢? 开始学的时候,也被此类问题困扰很久。其实相关的文章很多,感觉说清楚的不多。结构体到底怎样对齐?
157 0
|
编译器
内存对齐
环境 32位操作系统 通过结构体的内存字节对齐了解操作系统的内存对齐 在32位操作系统中, CPU默认读和写数据是按照4字节的方式 在一个结构体中, 在编译的时候, 编译器会根据结构体中的成员变量使其内存对齐, 让他们都是符合让CPU一次读取的数据而不用再读取一次数据, 减少了读取的次数 下面通过案例讲解 1.
700 0
|
存储 程序员 编译器
|
C++ 编译器 C语言