(转载) 内存对齐 (一) #pragma pack的用法及大小的计算
(源自:http://www.cppblog.com/deercoder/archive/2011/03/13/141717.html)
现在的一些处理器,需要你的数据的内存地址必须是对齐(align)的,即使不是必须,如果你对齐的话,运行的速度也会得到提升。虽然对齐会产生的额外内存空间,但相对于这个速度的提升来说,是值得的。所谓对齐,就是地址必须能整除一个整数,这个就是对齐参数(alignment value)。合法的取值范围是1、2、4、6、16、……、8192。怎样对齐呢?编译器帮你搞定。怎样设置编译器的对齐方式呢?用#pragma pack( n )和__declspec(align(#))。依据它俩,编译器是咋工作的?这个就是接下来要说的了。
1 #include <stdio.h>
2 #pragma pack( 1 )
3 struct A
4 {
5 char a;
6 short b;
7 char c;
8 };
9
10 int main()
11 {
12 printf("%d\n",sizeof(A));
13 return 0;
14 }
OK,下面对这个代码进行详细的分析。
用MSDN的话一言以蔽之:
“The alignment of a member (except the first one) will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.”
翻译成中文,也就是:
“结构体中的数据成员,除了第一个是始终放在最开始的地方,其它数据成员的地址必须是它本身大小或对齐参数两者中较小的一个的倍数。”
P.S:注意上面所说的后面一句话,也就是说,结构体的数据成员的地址必须是本身大小和对齐参数中较小的那一个。
(1)在pack为1的时候,对齐参数是1,那么我们对这个结构体每一元素进行分析。
char a; // 第一个元素在[0]位置处
short b; // short两个字节,地址是min(1,sizeof(short))的倍数,即1的倍数[1~2]
char c; // 地址应该是min(1,sizeof(1))的倍数,从而即为[3]
故在pack为1的时候,输出的结果应该是4([0~3]),其中所有的元素都填满了。
(2)在pack为2的时候,同样按照上面的方法,我们继续来分析下。
Char a; // 第一个占[0]位置。
Short b; // min(2,sizeof(short)),也就是必须为2的倍数,从而[2~3]
Char c; // min(2,sizeof(char)),也就是位1,地址为[4]
因此最后占据的大小是[0],[2~3],[4],整个结构体的大小size必须是2的倍数,所以应该是6(向上对齐至2的倍数)
(3)在pack为4的时候,同上,得到的结果是[0],[2~3],[4],因此也是6. 然后我们对上面的这个结构体变换一下顺序,可以得到。
struct B
{
char a;
char b;
short c;
};
在#pragma pack(4)的情况下,输出却是4(注:上面的输出时6)解释如下:
Char a; //占据一个字节,地址为【0】
Char b; //地址应该是min(4,sizeof(char)) = 1的倍数,也就是地址为【1】
Short c; //地址应该是min(4,sizeof(short)) = 2的倍数,也就是【2~3】
故总体占据的是【0~3】的连续单元,也就是4.至此,我们对#prgama pack(n)的用法和对应的判定方法有了一个全新的认识。
特别提出:
sizeof(ao.a )还是1,sizeof(ao.b )还是2。 如果struct B中含有A的一个对象m_a,
struct B
{
…
A m_a;
…
}
则这个m_a对齐参数是A中最大的数据类型的大小(这里是short的2)和n中较小者。如果这个对齐参数是B中最大的话,最后B的大小也会与这个对齐参数有关。m_a的对齐参数,由于是A的变量,所以采用A的对齐参数,也就是应该是A的最大元素个数和n中较小的值。而B的大小就要根据这个对齐参数来确定大小。
1 #include <iostream>(1)在pack为1的时候,由于min中有一个为1,所以都是相邻存放的。Sizeof(test)就是int+char+double的大小之和,即13.而对应的sizeof(B)则是一个int和一个struct之和。Int占4B,而struct的对齐参数则是Min(1,sizeof(max(A)),A中最大的元素师double类型的,也就是8,所以结果是min(1,8)=1,所以也是相邻存放的,而sizeof(A)的结果是13,所以直接是13+4 = 17.此时,sizeof(B)的大小是17.
2 #include <stdlib.h>
3
4 #define NUM 1
5 using namespace std;
6
7 #pragma pack ( 16 )
8
9 typedef struct
10 {
11 int a;
12 char b;
13 double c;
14 }test;
15
16 struct B
17 {
18 int a;
19 test b;
20 };
21 int main()
22 {
23 cout << "sizeof(int) = "<<sizeof(int) << endl;
24 cout << "sizeof(char) = " << sizeof(char) << endl;
25 cout << "sizeof(double) = " << sizeof(double) << endl;
26 cout << sizeof(test)<< endl;
27 cout << sizeof(B) << endl;
28 system("PAUSE");
29 return 0;
30 }
(2) 在pack为2的时候,此时min中有一个为2,对于test结构体,它的大小是4+2+8=14,因为在double的时候,min(2,8)=2,所以double类型的变量应该是2的倍数的地址,造成了char类型处空出了一个字节。总体就是14B。而对于B结构体而言,一个int占据4B,然后结构体的对齐参数采用min(2,max(A)),即min(2,8)= 2,由于是int,所以下一个地址是4,自然也是2的倍数,于是还是相邻存放。而A结构体的大小时14,于是B结构体的大小时14+4=18.
(3) 在pack为4的情况下。同样可以得到。此时对于A结构体的大小是4+4+8=16,因为double类型的必须是4的倍数,造成了char变量要占4个地方(实际只占一个,只是说这个地方空出来了3B),所以总体的大小为16.而同样对于B结构体,sizeof的结果是16+4 = 20,因为对于里面的成员要是min(4,8) = 4,而int恰好是4的倍数,所以相邻存放。于是就是16,20.
(4) 在pack为8的情况下(有所变化!!!),此时A结构体的大小是16,分析方法和上面相同,但是对于结构体B而言就有所区别,此时int还是4个字节,但是对于成员test结构体,它的对齐参数是min(8,max(A)) = min(8,sizeof(double) ) = 8也就是对齐参数是8,所以结构体变量要从地址为8开始,此时int就空出来了4B,从而最后求大小的时候应该是8+sizeof(A)= 8+16=24(最终测试结果如此)
(5)在pack为16的情况(以及以后的情况),结果是:A的大小为16B,而B的大小是24B。
总结:
(1)对于一个由简单类型组成的结构体,它的大小是由每一个成员变量的地址决定的。我们要按照定义的顺序,分别求出来地址开始的地方。从地址为0开始,每一个变量都采取min(n,sizeof(x))//x表示该变量的类型;来确定起始地址是多少的倍数,然后开始计数,直到填满该数据。最后求出来总的大小。而且在pack>=2的时候最终的大小需要时2的倍数,有时候需要向上取大为2的倍数。而在pack为1的情况则不需要。
(2)对于含有结构体成员的结构体,方法同上,只是在于对于结构体变量的对齐参数取法需要说明,具体就是min(n,结构体成员的最大元素的大小),就像上面的,结构体B中含有A成员,所以对齐参数就是min(n,sizeof(double))的大小,然后按照这个做法来取地址。
P.S:注意这里是pack而不是package,否则编译器会直接忽略#pragma package(),因为即使发生错误编译器也会直接忽略,而我们还是会默认认为编译器已经当做了字节按照n来处理。(某些博客上面的内容很容易让人误解或者晕倒!)
以上代码结果在Dev C++ , C-Free 5.0,VS 2010上均通过测试。
我喜欢程序员,他们单纯、固执、容易体会到成就感;面对困难,能够不休不眠;面对压力,能够迎接挑战。他们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想,用智慧把属于自己的事业开创。其实我是一个程序员[=.=]