pragma pack对齐方式详细介绍

来源:互联网 发布:面向切面编程 编辑:程序博客网 时间:2024/06/05 17:09

为了加快读写数据的速度,编译器采用数据对齐的方式来为每一个结构体分配空间。——写在开头

本文有自己的原创也有转载的博文,转载的部分一一列出来,可能不全请见谅这里这里这里这里等等。。。。。。

(更详细的解说:

       在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何 变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是内存对齐

内存对齐的原因:

1)某些平台只能在特定的地址处访问特定类型的数据;

2)提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。

win32平台下的微软C编译器对齐策略:

1)结构体变量的首地址能够被其最宽数据类型成员的大小整除。编译器在为结构体变量开辟空间时,首先找到结构体中最宽的数据类型,然后寻找内存地址能被该数据类型大小整除的位置,这个位置作为结构体变量的首地址。而将最宽数据类型的大小作为对齐标准。

2)结构体每个成员相对结构体首地址的偏移量(offset)都是每个成员本身大小的整数倍,如有需要会在成员之间填充字节。编译器在为结构体成员开辟空 间时,首先检查预开辟空间的地址相对于结构体首地址的偏移量是否为该成员大小的整数倍,若是,则存放该成员;若不是,则填充若干字节,以达到整数倍的要 求。

3)结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。如有需要会在最后一个成员末尾填充若干字节使得所占空间大小是最宽数据类型大小的整数倍。

)

在谈pragma pack之前,必须先知道数据类型在内存中占用的字节数。16位32位64位系统占用的字节数都有不同。
16位编译器
char :1个字节
char*(即指针变量): 2个字节
short int : 2个字节
int:  2个字节
unsigned int : 2个字节
float:  4个字节
double:   8个字节
long:   4个字节
long long:  8个字节
unsigned long:  4个字节

以在16位计算机中表示为例,基本数据类型加上修饰符有表1的描述。

类型说明长度(字节)表示范围备注char字符型

1
-128~127-27~(27-1)unsigned char无符号字符型
1
0~2550~(28-1)signed char有符号字符型
1
-128~127-27~(27_1)int整形
2
-32768~32767-215~(215-1)unsigned int无符号整形
2
0~655360~(216-1)int有符号整形
2
-32768~32767-215~(215-1)shord int短整形
2
-32768~32767-215~(215-1)unsigned shord int无符号短整形
2
0~655350~(216-1)signed shord int有符号短整形
2
-32768~32767-215~(215-1)long int长整形
4
-2147483648~2147483647-231~(231-1)unsigned long int无符号长整形
4
0~42949672960~(235-1)signed long int有符号长整形
4
-2147483648~2147483647-231~(231-1)float浮点型
4
-3.4×1038~-3.4×10387位有效位double双精度型
8
-1.7×10308~-1.7×1030815位有效位long double长双精度型
10
-3.4×104392~1.1×10439219位有效位

表1 常用基本数据类型描述


32位编译器
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int:  4个字节
unsigned int : 4个字节
float:  4个字节
double:   8个字节
long:   4个字节
long long:  8个字节

long double:  8个字节
unsigned long:  4个字节


64位编译器
char :1个字节
char*(即指针变量): 8个字节(64位的寻址空间是2^64, 即64个bit,也就是8个字节。)
short int : 2个字节
int:  4个字节
unsigned int : 4个字节
float:  4个字节
double:   8个字节
long:   8个字节
long long:  8个字节
unsigned long:  8个字节

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

=====================================================分割线==========================================================================

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

------------------------------------------------------

先看一个结构体的代码: //爱立信2011笔试 360 2011笔试均涉及

struct node{int a;int b;};

问:sizeof(Node)是多少? 答案很简单,在32位机器上,一个int是4个字节,两个int就是8个字节,sizeof(Node)就是8。

好的,上面那个答案确实是8,那么再看下面这个结构体:

struct node{char a;int b;};

问:这个时候sizeof(Node)又是多少呢? int是4个字节,char是1个字节,答案是5?

这回,没有那么幸运,经过在机器上的操作,答案是8! Why?

实际上,这不是语言的问题,你在ANSI C中找不到为什么会这样!甚至你在不同的体系结构、不同的编译器下会得到不同的答案。那么,到底是谁把5改成了8呢?

这就引入了一个概念,叫做“内存对齐”。所谓的内存对齐,是指一种计算机体系结构(如X86)对基本数据类型的存储位置有限制,要求其地址为某个数的倍数,通常这个数为4或8。这种要求会简化处理器的设计以及提升数据访问的效率。至于为什么会有这样的设计,简单的说访存总线的位数固定,以32位总线为例,地址总线的地址总是4对齐的,所以数据也四对齐的话,一个周期内就可以把数据读出。这里不理解的话可以跳过去,只要记得对齐这回事儿就行了。如果想更深入的理解,可以看这里另一篇文章。

知道这个之后,那么我们就可以理解,实际上是编译器为了效率,在相邻的变量之间放置了一些填充字节来保证数据的对齐。X86结构是4对齐的,所以sizeof(Node)是8不是5。

再来看一个例子:

struct node{int a;char b;char c;int d;char d;};

这时的sizeof(Node)是多少呢?没错,是16。

好的,既然我们知道对齐是由编译器来作的,那么我们可不可以更改对齐数呢? 答案是可以的,在C语言中,我们可以通过

#pragma pack(n)

来更改对齐模数。

注:以上都是在现x86 linux下使用gcc编译器验证,不乏有其他系统和编译器会得到不同的结果。

再让我们来看个例子:

struct node {double a;int b;int c;char d;};

这个时候的sizeof(node)是多少?20?24?

其实,这个时候你会发现,当你在windows上使用VC编译的时候,你会得到24;当你在linux上使用gcc编译的时候,你会得到20!其实,这恰好说明这种数据的对齐是由编译器决定的!在VC中规定, 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;而在gcc中规定对齐模数最大只能是4,也就是说,即使结构体中有double类型,对齐模数还是4,所以数据是按照1,2,4对齐的。所以,在两个不同编译器上,你得到了不同的答案!

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

熟悉c的人都知道,sizeof是一个关键字而不是一个宏或者库函数什么的,他的值是在编译时确定的,如果这个不了解,可以现看看这篇文章和这篇文章。 既然如此,让我们先看下面几个小例子:

sizeof(int);sizeof(char);sizeof(double);

上面三行sizeof的值是多少呢?这里我们假定在32位的x86系统下。我们会得到答案:4,1,8。这个没什么吧,大多数人都应该知道。那么,下面这个:

sizeof(int);sizeof(long);

在32位x86下,这两个是多少呢?4,8?

实际上,答案是4,4。我们需要注意,long类型在32位系统下是32位的。那么,64位下结果又如何呢?8,8?其实答案是4,8。另一个需要注意的是,64位下的int是32位的

上面只是热热身,现在,让我们看sizeof的下面几种情形:

1、sizeof一个结构体。

这个我就不说啥了,具体的参考这篇文章。至于空的结构体,下面会解释。

2、sizeof数组、指针等 先看下面两个例子:

char a[100];char b[100]="helloworld!";char c[]="helloworld!";char* d=b;sizeof(a);sizeof(b);sizeof(c);sizeof(d);

在32位x86系统下,以上各是多少呢?

答案是:100,100,12,4。

为什么不是100,12,12,12呢? sizeof一个数组名,返回的是数组的大小,不管你数组里面放的什么数据。所以,第一个数组大小是100,第二个数组大小是100,第三个数组大小是12(别忘记"\0")。第四个呢?第四个不是一个数组名,而是一个指针!32位下指针大小永远是4,不管你是指向一个数组还是一个int还是一个char。 好,这个问题搞清楚之后,看下面这个程序:

int func(char a[100]){printf("%d\n", sizeof(a));}int main(){char *m = "helloworld!";func(m);char n[100]="helloworld!";func(n);}

这个程序会打印出什么结果呢?

答案是:4, 4。 为什么结果都是4?不是应该返回数组长度么?

这里出现又一个需要注意的地方:在作为参数传递的时候,数组名会退化为指针。也就是说,这里的func里的参数,虽然看上去是个数组名,但实际上还是个指针。 你了解了么?下面是几个练习,自己实验下^_^

char *p = NULL;sizeof(p);sizeof(*p);int a[100];sizeof(a);sizeof(a[100]);sizeof(&a);sizeof(&a[0]);

懒得实验?答案分别是4,1,400,4,4,4。为什么?自己想想。

3、sizeof一些诡异的东西

(enum,空类,空struct) 所谓的诡异的东西,就是一些你想到的想不到的东西拿来sizeof。比如说sizeof一个enum类型是多少?一个空struct呢?一个空类呢? 这些诡异的东西在标准C中都没有作出规定,很大程度上都是编译器和系统结构相关的。 先来看一个:


这个你会得到什么样的结果呢?28? 在gcc或者vc下运行一把,你会得到答案:4。

为什么呢? 实际上,enum具体有多大取决于编译器的实现,目前大多数的编译器都将其实现为int类型。也就是说这里的enum被当作int类型(当然,使用上不一样)。这不是一成不变的,有些编译器,如VC++允许下面这种定义:

//注意这是个cpp文件enum Color : unsigned char{red, green, blue};// assert(sizeof(Color) == 1);

enum先说到这里,那么一个空的结构体是多大呢? 如果你擅长C语言,你可以很快的写出下面的程序:

//this is a *.c filestruct node{}Node;int main{printf("%d\n", sizeof(Node));}

很快,你可以验证出来结果是0。 没错,这很好理解。但是,如果你用的是c++呢?

//this is a *.cpp filestruct node{}Node;class node2{}Node2;int main(){printf("%d\n", sizeof(Node));printf("%d\n", sizeof(Node2));}

(不怎么会写c++的我表示压力很大)以上这个c++的例子结果是多少呢?

为什么会得到1,1这个结果呢? 换句话说就是为什么sizeof一个空类和空结构体在c++下就是1呢?

这个原因要追朔到c++标准中的一句话:“no object shall have the same address in memory as any other variable”, 用中国话简单点说就是:不同的对象之间应该有不同的地址(为什么会有这样的规定?看这里)。

既然每个对象都必须有不同的地址,让我们假设上面代码中的Node的size是0,想想会出现什么样的后果?

Node a;Node b;

a的大小是0,b的大小是0,那么,a和b的地址是不是很可能重复了?

所以,为了保证不同的对象拥有不同的地址,最简单的方法就是保证所有类型的大小都不是0

所以,为了保证这点,大多数c++编译器都会给空结构或类加上一个冗余的字节保证类型不为空。

我的解释的清楚么?如果我的表达能力不够好,看这里,解释的足够详细。 当然,还有其他很多我没有想到的诡异的东西可以拿来sizeof,如果你想到了,欢迎跟我分享。至于c++中的sizeof类,基类,派生类,足够可以作为一个专门的文章了,先不再这里说,等我学会C++的再写一下相关内容(-_-")。

最后,给一个稍微给力点的程序,大家看看最终得到的结果是什么(注意这是一个c++程序):

//this is a cpp filetypedef   struct   weekday_st{enum   week     {sun=123456789,mon,tue,wed,thu,fri,sat,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,ab,ac,ad,ae,af,ag,ah,ai,aj,ak};enum   day{monring,   moon,   aftermoon};}weekday_st;int   main(int   argc,   char   *argv[]){printf( "sizeof(weekday_st)=%d\n ",   sizeof(weekday_st));printf( "sizeof(weekday)=%d\n ",   sizeof(weekday_st::week));printf( "sizeof(day)=%d\n ",   sizeof(weekday_st::day));return  0;}

答案是:1 4 4

------------------------------------------------------

普通的对齐方式,先上几个示例吧:


上图得到的结果是占用四个字节,原因是short本身2字节,char一个字节,按最长的字节数对齐。所以很容易得到答案。


如果是上图所示,结果并不是5,而是6。因为short占用两个字节,为最长字符长度。结构体变量所占空间的大小必定是最宽数据类型大小的整数倍。所以,总的字节数不可能是5,那应该怎样排呢?


如上图所示,这个不难理解。


  

对应的结构体:

typedef struct node{char a;double b;short c;};
sizeof得到的结果为24
同理,下面的类型也是24

 

而下图这种情况自然只有16了

  

但是如果在添加一个char型数据,按照最长数据对齐的话,结果会变成24,尽管char只有一个字节。

  

下面结构同样是24

 

下面结构占用字节数为3,因为最大的占用字节数为1.三个元素。为三。


看完上面的这些例子之后。对基本的结构体中对齐方式应该有一个大概的了解了,总结两条:

第一条:一般情况下,查看最宽数据类型大小,并以最宽数据占用的内存大小对齐

第二条:最后得到的结构体变量所占空间的大小必定是最宽数据类型大小的整数倍

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

直接上例子(默认32位编译器):

struct Test{char x1; //第一个成员,放在[0]位置,short x2; //第二个成员,自身长度为2,按2字节对齐,所以放在偏移[2,3]的位置,float x3; //第三个成员,自身长度为4,按4字节对齐,所以放在偏移[4,7]的位置,char x4; //第四个成员,自身长度为1,按1字节对齐,所以放在偏移[8]的位置,};

如果是直接累加结构体中的字节数得到的是1+2+4+1=8,可想,这个思路是完全错误的

如果按照上面注释中的思路,整个结构体的实际内存消耗是9个字节,但没有考虑结构整体的对齐方式。

注释中的思路图示:


一目了然吧!

这种图示方式也是错误的:


原因是:不要什么结构都按照八字节宽度对齐。这里 结构体中最宽的数据类型是float只有4个字节。正确的对齐方式是:


整个结构占用的空间是12个字节。



至于空结构体

比如:

空结构体

typedef struct node{}S;

则sizeof(S)=1;或sizeof(S)=0;

在C++中占1字节,而在C中占0字节。

解释为:

对于结构体和空类大小是1这个问题,首先这是一个C++问题,在C语言下空结构体大小为0(当然这是编译器相关的)。这里的空类和空结构体是指类或结构体中没有任何成员。

在C++下,空类和空结构体的大小是1(编译器相关),这是为什么呢?为什么不是0?

这是因为,C++标准中规定,“no object shall have the same address in memory as any other variable” ,就是任何不同的对象不能拥有相同的内存地址。 如果空类大小为0,若我们声明一个这个类的对象数组,那么数组中的每个对象都拥有了相同的地址,这显然是违背标准的。

但是,也许你还有一个疑问,为什么C++标准中会有这么无聊的规定呢?

当然,这样规定显然是有原因的。我们假设C++中有一个类型T,我们声明一个类型T的数组,然后再声明一个T类型的指针指向数组中间某个元素,则我们将指针减去1,应该得到数组的另一个索引。如下代码:

T array[5];int diff = &array[3] - &array[2];

上面的代码是一种指针运算,将两个指针相减,编译器作出如下面式子所示的动作:

diff = ((char *)&array[3] - (char *)&array[2]) / sizeof T;

式子应该不难懂把,很明显的一点就是这个式子的计算依赖于sizeof T。虽然上面只是一个例子,但是基本上所有的指针运算都依赖于sizeof T。

好,下面我们来看,如果允许不同的对象有相同的地址将会引发什么样的问题,看下面的例子:

&array[3] - &array[2] = &array[3] - &array[1]= &array[3] - &array[1]= &array[3] - &array[0]= 0

我们可以看到,在这个例子中,如果每个对象都拥有相同地址,我们将没有办法通过指针运算来区分不同的对象。还有一个更严重的问题,就是如果 sizeof T是0,就会导致编译器产生一个除0的操作,引发不可控的错误。

基于这个原因,如果允许结构体或者类的大小为0,编译器就需要实现一些复杂的代码来处理这些异常的指针运算。

所以,C++标准规定不同的对象不能拥有相同的地址。那么怎样才能保证这个条件被满足呢?最简单的方法莫过于不允许任何类型的大小为0。所以编译器为每个空类或者空结构体都增加了一个虚设的字节(有的编译器可能加的更多),这样这些空类和空结构的大小就不会是0,就可以保证他们的对象拥有彼此独立的地址。 

参考于这里

还举几个例子:

typedef struct node1 {     int a;     char b;     short c; }S1;
结果为4+1+[ ]+2 = 8,结果为8

typedef struct node2 {     char a;     int b;     short c; }S2;
结果为:1+[ ]+[ ]+[ ]+4+2+[ ]+[ ] = 12,结果为12.

typedef struct node3 {     int a;     short b;     static int c; }S3;
这一题含有静态数据成员c

静态数据成员的存放位置与结构体实例的存储地址无关(注意只有在C++中结构体中才能含有静态数据成员,而C中结构体中是不允许含有静态数据成员的)。

静态变量c是单独存放在静态数据区的,因此用siezof计算其大小时没有将c所占的空间计算进来。

具体计算如下:

4+2+[ ]+[ ] = 8,结果为8

typedef struct node4 {     bool a;     S1 s1;     short b; }S4;
这道题结构体中包含结构体

而结构体S1上面已经求出来是占用字节数是8

那么S4占用的字节数具体如下:

因为S1中最宽数据字节数为4,示意图如下:


显然,最后结果是1+[ ]+[ ]+[ ]+8+2+[ ]+[ ] = 16,结果为16

同理;

typedef struct node5 {     bool a;     S1 s1;     double b;     int c; }S5;
1+[ ]+[ ]+[ ]+[ ]+[ ]+[ ]+[ ]+8+8+4+[ ]+[ ]+[ ]+[ ]= 32,结果为32


接下来说说

在程序中使用#pragma pack(n)命令强制以n字节对齐时的情况

比较n和结构体中最长数据类型所占的字节大小,取两者中小的一个作为对齐标准。默认为8

若需取消强制对齐方式,则可用命令#pragma pack()将当前字节对齐值设为默认值(通常是8) 。

如果在程序开头使用命令#pragma pack(4),对于下面的结构体

typedef struct node5 {     bool a;     S1 s1;     double b;     int c; }S5;
对齐方式:

|-----------a--------| 4字节

|--------s1----------| 4字节

|--------s1----------| 4字节

|--------b-----------| 4字节

|--------b-----------| 4字节

|---------c----------| 4字节




重要规则:
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;
2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;
3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;
4,复杂类型(如结构)整体的对齐是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样在成员是复杂类型时,可以最小化长度;
5,结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;


更改c编译器的缺省字节对齐方式:

方法一:
使用#pragma pack(n),指定c编译器按照n个字节对齐;
使用#pragma pack(),取消自定义字节对齐方式。

方法二:
__attribute(aligned(n)),让所作用的数据成员对齐在n字节的自然边界上;如果结构中有成员的长度大于n,则按照最大成员的长度来对齐;
__attribute((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

综上所述,下面给出例子并详细分析:


#pragma pack(8)struct s1{short a; //第一个,放在[0,1]位置,long b; //第二个,自身长度为4,按min(4, 8) = 4对齐,所以放在[4,7]位置};
结构体的实际内存消耗是8个字节,结构体的对齐是min( sizeof( long ), pack_value ) = 4字节,所以整个结构占用的空间是8个字节。

struct s2{char c; //第一个,放在[0]位置,s1 d; //第二个,根据规则四,对齐是min( 4, pack_value ) = 4字节,所以放在[4,11]位置,long long e; //第三个,自身长度为8字节,所以按8字节对齐,所以放在[16,23]位置,};
所以实际内存消耗是24自己,整体对齐方式是8字节,所以整个结构占用的空间是24字节。


#pragma pack(4)class TestC{   public:    char a; //第一个成员,放在[0]偏移的位置,  short b; //第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。  char c; //第三个,自身长为1,放在[4]的位置。};
整个类的实际内存消耗是5个字节,整体按照min( sizeof( short ), 4 ) = 2对齐,所以结果是2的整数倍:sizeof( TestC ) = 6;


#pragma pack(2)class TestB{public: int aa; //第一个成员,放在[0,3]偏移的位置,  char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。  short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。  char c; //第四个,自身长为1,放在[8]的位置。};
结构整体的对齐是min( sizeof( int ), pack_value ) = 2,所以sizeof( TestB ) = 10;

#pragma pack(4)class TestB{public: int aa; //第一个成员,放在[0,3]偏移的位置,  char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。  short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。  char c; //第四个,自身长为1,放在[8]的位置。};
类实际占用的内存空间是9个字节。根据规则5,结构整体的对齐是min( sizeof( int ), pack_value ) = 4,所以sizeof( TestB ) = 12;


总结一下,在计算sizeof时主要注意一下几点:

1)若为空结构体,则只占1个字节的单元 (C++中)

2)若结构体中所有数据类型都相同,则其所占空间为 成员数据类型长度×成员个数

若结构体中数据类型不同,则取最长数据类型成员所占的空间为对齐标准,数据成员包含另一个结构体变量t的话,则取t中最 长数据类型与其他数据成员比较,取最长的作为对齐标准,但是t存放时看做一个单位存放,只需看其他成员即可。

3)若使用了#pragma pack(n)命令强制对齐标准,则取n和结构体中最长数据类型占的字节数两者之中的小者作为对齐标准。


另外除了结构体中存在对齐之外,普通的变量存储也存在字节对齐的情况,即自身对齐。编译器规定:普通变量的存储首地址必须能被该变量的数据类型宽度整除。


补充一下,对于数组,比如:
char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.


#pragma pack(push)#pragma pack(pop)

push是将当前对齐的方式压栈,pop是将栈中的对齐方式弹出

#pragma pack(push)#pragma pack(1) //注意,此处开始了哟struct test1{char a;int b;char c;};#pragma pack(pop)//注意,此处结束了哟struct test2{char a;int b;char c;};int main(){/*此处省略*/}//那么,你用test1定义的变量大小就是6,用test2定义的变量大小就是12。

#pragma pack(push, n)

先将当前字节对齐值压入编译栈栈顶, 然后再将 n 设为当前值。

#pragma pack(pop, n)
将编译栈栈顶的字节对齐值弹出, 然后丢弃, 再将 n 设为当前值。


#pragma pack(n)和#pragma pop()

struct sample{char a;double b;};

当sample结构没有加#pragma pack(n)的时候,sample按最大的成员那个对齐;

(所谓的对齐是指对齐数为n时,对每个成员进行对齐,既如果成员a的大小小于n则将a扩大到n个大小;

如果a的大小大于n则使用a的大小;)所以上面那个结构的大小为16字节.

当sample结构加#pragma pack(1)的时候,sizeof(sample)=9字节;无空字节。

(另注:当n大于sample结构的最大成员的大小时,n取最大成员的大小。

所以当n越大时,结构的速度越快,大小越大;反之则)

#pragma pop()就是取消#pragma pack(n)的意思了,也就是说接下来的结构不用#pragma pack(n)


#include <iostream>using namespace std;#pragma pack(4)typedef struct TestB { int d; char a; short b; char c; char e; };  void main(){cout<<sizeof(TestB)<<endl;system("pause");}
结果是12


#pragma pack(4)
  class TestB
  {
         public:
    int aa; //第一个成员,放在[0,3]偏移的位置,
    char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
    short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
    char c; //第四个,自身长为1,放在[8]的位置。
  };
这个类实际占据的内存空间是9字节
类之间的对齐,是按照类内部最大的成员的长度,和#pragma pack规定的值之中较小的一个对齐的。
所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。
9按照4字节圆整的结果是12,所以sizeof(TestB)是
12

更详细的介绍:

不光结构体存在内存对齐一说,类(对象)也如此,甚至于所有变量在内存中的存储也有对齐一说(只是这些对程序员是透明的,不需要关心)。实际上,这种对齐是为了在空间与复杂度上达到平衡的一种技术手段,简单的讲,是为了在可接受的空间浪费的前提下,尽可能的提高对相同运算过程的最少(快)处理。先举个例子:

    假设机器字长是32位的(即4字节,下面示例均按此字长),也就是说处理任何内存中的数据,其实都是按32位的单位进行的。现在有2个变量:    

char A; int B; 

     假设这2个变量是从内存地址0开始分配的,如果不考虑对齐,应该是这样存储的(见下图,以intel上的little endian为例,为了形象,每16个字节分做一行,后同):


    因为计算机的字长是4字节的,所以在处理变量A与B时的过程可能大致为:

    A:将0x00-0x03共32位读入寄存器,再通过左移24位再右移24位运算得到a的值(或与0x000000FF做与运算)

    B:将0x00-0x03这32位读入寄存器,通过位运算得到低24位的值;再将0x04-0x07这32位读入寄存器,通过位运算得到高8位的值;再与最先得到的24位做位运算,才可得到整个32位的值。

    上面叙述可知,对a的处理是最简处理,可对b的处理,本身是个32位数,处理的时候却得折成2部分,之后再合并,效率上就有些低了。

    想解决这个问题,就需要付出几个字节浪费的代价,改为下图的分配方式:


    按上面的分配方式,A的处理过程不变;B却简单得多了:只需将0x04-0x07这32位读入寄存器就OK了。

    我们可以具体谈结构体或类成员的对齐了:

    结构体在编译成机器代码后,其实就没有本身的集合概念了,而类,实际上是个加强版的结构体,类的对象在实例化时,内存中申请的就是一些变量的空间集合(类似于结构体,同时也不包含函数指针)。这些集合中的每个变量,在使用中,都需要涉及上述的加工原则,自然也就需要在效率与空间之间做出权衡。

    为了便捷加工连续多个相同类型原始变量,同时简化原始变量寻址,再汇总上述最少处理原则,通常可以将原始变量的长度做为针对此变量的分配单位,比如内存可用64个单元,如果某原始变量长度为8字节,即使机器字长为4字节,分配的时候也以8字节对齐(看似IO次数是相同的),这样,寻址、分配时,均可以按每8字节为单位进行,简化了操作,也可以更高效。

    系统默认的对齐规则,追求的至少两点:1、变量的最高效加工 2、达到目的1的最少空间 

    举个例子,一个结构体如下:
 

//by www.datahf.net zhangyutypedef struct T{     char c; //本身长度1字节     __int64 d;  //本身长度8字节    int e;  //本身长度4字节    short f;  //本身长度2字节    char g;  //本身长度1字节    short h;  //本身长度2字节}; 

    假设定义了一个结构体变量C,在内存中分配到了0x00的位置,显然:

    对于成员C.c  无论如何,也是一次寄存器读入,所以先占一个字节。

    对于成员C.d  是个64位的变量,如果紧跟着C.c存储,则读入寄存器至少需要3次,为了实现最少的2次读入,至少需要以4字节对齐;同时对于8字节的原始变量,为了在寻址单位上统一,则需要按8字节对齐,所以,应该分配到0x08-0xF的位置。

    对于成员C.e  是个32位的变量,自然只需满足分配起始为整数个32位即可,所以分配至0x10-0x13。

    对于成员C.f  是个16位的变量,直接分配在0x14-0x16上,这样,反正只需一次读入寄存器后加工,边界也与16位对齐。

    对于成员C.g  是个8位的变量,本身也得一次读入寄存器后加工,同时对于1个字节的变量,存储在任何字节开始都是对齐,所以,分配到0x17的位置。

    对于成员C.h  是个16位的变量,为了保证与16位边界对齐,所以,分配到0x18-0x1A的位置。

    分配图如下(还不正确,耐心读下去):


    结构体C的占用空间到h结束就可以了吗?我们找个示例:如果定义一个结构体数组 CA[2],按变量分配的原则,这2个结构体应该是在内存中连续存储的,分配应该如下图:
 

    分析一下上图,明显可知,CA[1]的很多成员都不再对齐了,究其原因,是结构体的开始边界不对齐。

    那结构体的开始偏移满足什么条件才可以使其成员全部对齐呢。想一想就明白了:很简单,保证结构体长度是原始成员最长分配的整数倍即可。
    上述结构体应该按最长的.d成员对齐,即与8字节对齐,这样正确的分配图如下:


    当然结构体T的长度:sizeof(T)==0x20;

     再举个例子,看看在默认对齐规则下,各结构体成员的对齐规则:

typedef struct A {     char c;  //1个字节    int d;  //4个字节,要与4字节对齐,所以分配至第4个字节处    short e;  //2个字节, 上述两个成员过后,本身就是与2对齐的,所以之前无填充 }; //整个结构体,最长的成员为4个字节,需要总长度与4字节对齐,所以, sizeof(A)==12 typedef struct B {     char c;  //1个字节    __int64 d;  //8个字节,位置要与8字节对齐,所以分配到第8个字节处    int e;  //4个字节,成员d结束于15字节,紧跟的16字节对齐于4字节,所以分配到16-19    short f;  //2个字节,成员e结束于19字节,紧跟的20字节对齐于2字节,所以分配到20-21    A g;  //结构体长为12字节,最长成员为4字节,需按4字节对齐,所以前面跳过2个字节, //到24-35字节处    char h;   //1个字节,分配到36字节处    int i;   //4个字节,要对齐4字节,跳过3字节,分配到40-43 字节}; //整个结构体的最大分配成员为8字节,所以结构体后面加5字节填充,被到48字节。故://sizeof(B)==48;

    具体的分配图如下:

 

-----------------------------------------------------------------------------------------------------------------------------------







0 0
原创粉丝点击