#pragma pack(n)

来源:互联网 发布:大学生最火话题知乎 编辑:程序博客网 时间:2024/05/29 06:51

现在的一些处理器,需要你的数据的内存地址必须是对齐(align)的,即使不是必须,如果你对齐的话,运行的速度也会得到提升。虽然对齐会产生的额外内存空间,但相对于这个速度的提升来说,是值得的。

所谓对齐,就是地址必须能整除一个整数,这个就是对齐参数(alignment value)。合法的取值范围是1、2、4、6、16、……、8192。

怎样对齐呢?编译器帮你搞定。

一、在不设置对齐参数时(自然对界)

建立一个结构体时,必须考虑其成员变量的数据对齐,来尽可能减少空间的浪费,把占用空间小的元素放在struct/class的前面。

struct tagTest

{

double b;

char c;

int a;

char d;

}

这个结构体,由于 double b 排在最前面,所以该结构体以double(该结构体中最宽的数据类型)作为数据对齐的方式,double为8个字节的空间,则计算整个结构体的大小时,如果没有达到8的倍数,会自动填充字节到8的倍数。

另外,结构体中的每个数据成员的起始地址,必须是自身数据类型大小的整数倍。拿上面的例子来说,设b的其实地址为0,c的其实地址就为8,但是a的起始地址就不能为9,因为a为int类型,大小为4个字节,编译器会在c后边填充3个字节,使a的其实地址为12。最后,d的起始地址为16。

由于8(b的大小)+4(c的大小加上填充的3字节)+4(a的大小)+1(d的大小)=17,17不是8的倍数,编译器会自动填充字节到8的倍数,即24,这时的结果则为24

调整数据结构成员后(把占用空间小的元素放在前面)

struct tagTest

{

char c;

char d;

int a;

double b;

}

计算字节的方式:1(c的大小)+1(d的大小)+2(字节填充)+4(a的大小)+8(b的大小)=16

用法:把struct/class里的成员变量的大小排个序,按照从小到大的顺序写入结构体内,则能尽可能节省内存空间。

特殊情况:

有的class/struct内的成员变量具有上下文关系,我们可以把相关的成员存储在一起,以便于理解和维护,建议不按照上面的用法来节省内存空间。

二、指定对界

用#pragma pack( n )和__declspec(align(#))。

依据它俩,编译器是咋工作的?这个就是接下来要说的了。

#include <stdio.h>
#pragma pack( 1 )
struct A
{             
    char a;
    short b;
    char c;
};

int main()
{
    printf("%d\n",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:注意上面所说的后面一句话,也就是说,结构体的数据成员的地址必须是本身大小和对齐参数中较小的那一个。

  结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。

 

(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 <iostream>
#include <stdlib.h>

#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) = "<<sizeof(int) << endl;
    cout << "sizeof(char) = " << sizeof(char) << endl;
    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。

三、union对齐

其实union(共用体)的各个成员是以同一个地址开始存放的,每一个时刻只可以存储一个成员,这样就要求它在分配内存单元时候要满足两点:   
  1.一般而言,共用体类型实际占用存储空间为其最长的成员所占的存储空间;   
  2.若是该最长的存储空间对其他成员的元类型(如果是数组,取其类型的数据长度,例int   a[5]为4)不满足整除关系,该最大空间自动延伸;   
    
  我们来看看这段代码:   
  union   mm{   
  char   a;//元长度1   
  int   b[5];//元长度4   
  double   c;//元长度8   
  int   d[3];   
  };   
  本来mm的空间应该是sizeof(int)*5=20;但是如果只是20个单元的话,那可以存几个double型(8位)呢?两个半?当然不可以,所以mm的空间延伸为既要大于20,又要满足其他成员所需空间的整数倍,即24   
    
  所以union的存储空间先看它的成员中哪个占的空间最大,拿他与其他成员的元长度比较,如果可以整除,ok


总结:

(1)       对于一个由简单类型组成的结构体,它的大小是由每一个成员变量的地址决定的。我们要按照定义的顺序,分别求出来地址开始的地方。从地址为0开始,每一个变量都采取min(n,sizeof(x))//x表示该变量的类型;来确定起始地址是多少的倍数,然后开始计数,直到填满该数据。最后求出来总的大小。而且在pack>=2的时候最终的大小需要时2的倍数,有时候需要向上取大为2的倍数。而在pack为1的情况则不需要。

(2)       对于含有结构体成员的结构体,方法同上,只是在于对于结构体变量的对齐参数取法需要说明,具体就是min(n,结构体成员的最大元素的大小),就像上面的,结构体B中含有A成员,所以对齐参数就是min(n,sizeof(double))的大小,然后按照这个做法来取地址。

参考于http://www.cppblog.com/deercoder/archive/2011/03/13/141717.html

http://blog.csdn.net/cyxcw1/article/details/9080519

http://www.cnblogs.com/leesy/archive/2010/10/21/1857744.html

0 0
原创粉丝点击