内存对齐问题
2009-10-10 08:47
190 查看
首先由一个程序引入话题:
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> 1 //环境:vc6 + windows sp2
2 //程序1
3 #include <iostream>
4
5 using namespace std;
6
7 struct st1
8 {
9 char a ;
10 int b ;
11 short c ;
12 };
13
14 struct st2
15 {
16 short c ;
17 char a ;
18 int b ;
19 };
20
21 int main()
22 {
23 cout<<"sizeof(st1) is "<<sizeof(st1)<<endl;
24 cout<<"sizeof(st2) is "<<sizeof(st2)<<endl;
25 return 0 ;
26 }
27
程序的输出结果为:
sizeof(st1) is 12
sizeof(st2) is 8
问题出来了,这两个一样的结构体,为什么sizeof的时候大小不一样呢?
本文的主要目的就是解释明白这一问题。
内存对齐,正是因为内存对齐的影响,导致结果不同。
对于大多数的程序员来说,内存对齐基本上是透明的,这是编译器该干的活,编译器为程序中的每个数据单元安排在合适的位置上,从而导致了相同的变量,不同声明顺序的结构体大小的不同。
那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7,4(int)
+ 2(short) + 1(char) = 7 。经过内存对齐后,结构体的空间反而增大了。
在解释内存对齐的作用前,先来看下内存对齐的规则:
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编
译命令#pragma pack(n),n=1,2,4,8,16
来改变这一系数,其中的n
就是你要指定的“对齐系数”。VC6默认8字节对齐
为0
的地方,以后每个数据成员的对齐按照#pragma pack
指定的数值和这个数据成员自身长度中,比较小的那个进行。
min( #pragma pack()指定的数,这个数据成员的自身长度
)
行对齐,对齐将按照#pragma pack
指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
min(#pragma pack()指定的数,结构(或联合)最大数据成员长度)。结构总大小必须为它的倍数
颗推断:当#pragma pack
的n
值等于或超过所有数据成员长度的时候,这个n
值的大小将不产生任何效果。
一个位域必须存储在同一个字节中,不能跨两个字节。
以程序1为例解释对齐的规则
:
St1 :char占一个字节,起始偏移为0
,int 占4个字节,min(#pragma pack()指定的数,这个数据成员的自身长度)
= 4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。short占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。到此规则1的数据成员对齐结束,此时的内存状态为:
oxxx|oooo|oo
0123 4567 89
(地址)
(x表示额外添加的字节)
共占10个字节。还要继续进行结构本身的对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragma
pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,需添加2个额外字节使结构的总大小为12
。此时的内存状态为:
oxxx|oooo|ooxx
0123 4567 89ab (地址)
到此内存对齐结束。St1占用了12个字节而非7个字节。
St2 的对齐方法和st1相同,读者可自己完成。
内存对齐的主要作用是:
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:经过内存对齐后,CPU的内存访问速度大大提升。具体原因稍后解释。
图一:
这是普通程序员心目中的内存印象,由一个个的字节组成,而CPU并不是这么看待的。
图二:
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory
access granularity(粒度) 本人把它翻译为“内存读取粒度” 。
(32位操作系统内存地址应该是以4为增量,程序中int占4字节只能说他充分利用了这4字节,而bool型剩下的高位3字节应该是不使用的,待验证)
假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
数据从0字节开始
数据从1字节开始
再次假设内存读取粒度为4。
图三:
当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。
图四:
此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。
这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接罢工了。
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
--> 1 //环境:vc6 + windows sp2
2 //程序1
3 #include <iostream>
4
5 using namespace std;
6
7 struct st1
8 {
9 char a ;
10 int b ;
11 short c ;
12 };
13
14 struct st2
15 {
16 short c ;
17 char a ;
18 int b ;
19 };
20
21 int main()
22 {
23 cout<<"sizeof(st1) is "<<sizeof(st1)<<endl;
24 cout<<"sizeof(st2) is "<<sizeof(st2)<<endl;
25 return 0 ;
26 }
27
程序的输出结果为:
sizeof(st1) is 12
sizeof(st2) is 8
问题出来了,这两个一样的结构体,为什么sizeof的时候大小不一样呢?
本文的主要目的就是解释明白这一问题。
内存对齐,正是因为内存对齐的影响,导致结果不同。
对于大多数的程序员来说,内存对齐基本上是透明的,这是编译器该干的活,编译器为程序中的每个数据单元安排在合适的位置上,从而导致了相同的变量,不同声明顺序的结构体大小的不同。
那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7,4(int)
+ 2(short) + 1(char) = 7 。经过内存对齐后,结构体的空间反而增大了。
在解释内存对齐的作用前,先来看下内存对齐的规则:
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编
译命令#pragma pack(n),n=1,2,4,8,16
来改变这一系数,其中的n
就是你要指定的“对齐系数”。VC6默认8字节对齐
规则1:
数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0
的地方,以后每个数据成员的对齐按照#pragma pack
指定的数值和这个数据成员自身长度中,比较小的那个进行。
min( #pragma pack()指定的数,这个数据成员的自身长度
)
规则2:
结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack
指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
min(#pragma pack()指定的数,结构(或联合)最大数据成员长度)。结构总大小必须为它的倍数
规则3:
结合1、2颗推断:当#pragma pack
的n
值等于或超过所有数据成员长度的时候,这个n
值的大小将不产生任何效果。
一个位域必须存储在同一个字节中,不能跨两个字节。
以程序1为例解释对齐的规则
:
St1 :char占一个字节,起始偏移为0
,int 占4个字节,min(#pragma pack()指定的数,这个数据成员的自身长度)
= 4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。short占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。到此规则1的数据成员对齐结束,此时的内存状态为:
oxxx|oooo|oo
0123 4567 89
(地址)
(x表示额外添加的字节)
共占10个字节。还要继续进行结构本身的对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragma
pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,需添加2个额外字节使结构的总大小为12
。此时的内存状态为:
oxxx|oooo|ooxx
0123 4567 89ab (地址)
到此内存对齐结束。St1占用了12个字节而非7个字节。
St2 的对齐方法和st1相同,读者可自己完成。
内存对齐的主要作用是:
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:经过内存对齐后,CPU的内存访问速度大大提升。具体原因稍后解释。
图一:
这是普通程序员心目中的内存印象,由一个个的字节组成,而CPU并不是这么看待的。
图二:
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory
access granularity(粒度) 本人把它翻译为“内存读取粒度” 。
(32位操作系统内存地址应该是以4为增量,程序中int占4字节只能说他充分利用了这4字节,而bool型剩下的高位3字节应该是不使用的,待验证)
假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
数据从0字节开始
数据从1字节开始
再次假设内存读取粒度为4。
图三:
当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。
图四:
此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。
这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接罢工了。
相关文章推荐
- 结构体内的内存对齐问题
- 内存字节对齐问题
- Android平台,C/C++代码内存对齐问题(signal SIGBUS Error)
- VC中结构体内存分配问题透析(“字节对齐”访问数据)
- c++内存中字节对齐问题详解 [ 转载 ]
- 内存对齐的问题1
- 内存对齐问题(原创)(看完记得回帖)
- 关于内存对齐问题的一些资料整理
- C++:struct和union 内存字节对齐问题
- sizeof()与内存对齐问题小探讨
- 关于内存对齐问题(二)
- 再谈内存对齐问题
- (原创)VB调用DLL(VC)使用结构体参数时的内存对齐及分配的问题.
- 内存对齐问题
- 内存对齐问题引起的添加NVRAM数据块失败
- #pragma pack(n)------内存对齐问题
- Arm结构体gcc内存边界对齐问题(zt)
- 内存对齐问题
- gcc 中结构体(struct)内存对齐问题分析
- 内存对齐问题