【内存对齐(一)】#pragma pack的用法及大小的计算
扫描二维码
随时随地手机看文章
所谓对齐,就是地址必须能整除一个整数,这个就是对齐参数(alignment value)。合法的取值范围是1、2、4、6、16、……、8192。
怎样对齐呢?编译器帮你搞定。
怎样设置编译器的对齐方式呢?用#pragma pack( n )和__declspec(align(#))。
依据它俩,编译器是咋工作的?这个就是接下来要说的了。#include
#pragma pack( 1 )
struct A
{
char a;
short b;
char c;
};
int main()
{
printf("%dn",sizeof(A));
return 0;
}
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的大小就要根据这个对齐参数来确定大小。
#include
#include
#define NUM 1
using namespace std;
#pragma pack ( 16 )
typedef struct {
int a;
char b;
double c;
}test;
struct B
{
int a;
test b;
};
int main()
{
cout << "sizeof(int) = "<
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << sizeof(test)<< endl;
cout << sizeof(B) << endl;
system("PAUSE");
return 0;
}
(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) 在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上均通过测试。
附注:在默认情况下,linux操作系统是以4字节对齐,windows操作系统则是以最大的内置类型对齐。
作用:指定结构体、联合以及类成员的packing alignment;
语法:#pragma pack( [show] | [push | pop] [, identifier], n )
说明:
1,pack提供数据声明级别的控制,对定义不起作用;
2,调用pack时不指定参数,n将被设成默认值;
3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降。
语法具体分析:
1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;
2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;
3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal
compiler stack,则pop操作被忽略;
4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。
#include
int main()
{
struct B{
char b; //对其系数1
int a; //对其系数4
short c; //对其系数2
};//整体对其系数4
/*
struct A{
char b; //对其系数1
int a; //对其系数4
struct B f;
short c; //对其系数2
};//整体对其系数4*/
#pragma pack(push) //保存对齐状态
#pragma pack (2)//无法识别的预处理命令
struct person{
/*char dda;
double dda1;
int type;
*/
char b;//整体对其系数1
double c; //整体对其系数8
char age;//整体对其系数1
};//整体对其系数8
#pragma pack(pop)//恢复对齐状态
struct room{
char chair[5];//整体对其系数1
int computer;//整体对其系数1
struct person children;//整体对其系数8
};
int size_r = sizeof(struct room);
int size_p = sizeof(struct person);
printf("size_r = %dnsize_p = %d", size_r,size_p);
getchar();
}