[zz]内存对齐

http://blog.sina.com.cn/s/blog_58649eb30100v2tc.html

 

主要讲2种内存对齐形式,分别对应不同的需求:

1、#pragma pack(n): 常用来控制接口对齐形式(n=1)
2、__declspec(align(n)):常用来组织结构体内部的对齐方式(算法中用)

上面讲到了关于pack的内存对齐和计算方法,这里继续讲实现内存对齐的另一种方式:__declspec( align(#) )

__declspec( align(#) )#pragma pack( n )有密切联系。

当一个变量或结构体同时受两者影响时,前者的优先级高。

成员的地址决定于前者及后者,其要么是前者的倍数,要么是后者的倍数,要么是成员的大小的倍数,取最小。

结构体最后的大小于前者有关,其要么是前者的倍数,要么是结构体中最大偏移量的倍数,取最大。

要算出最后结果,必须知道两者的值或缺省值。

 

下面举一个例子来详细的分析:

 

#include <stdio.h>

#include 
"stdafx.h"
#include 
<stdlib.h>
//using namespace std;

#pragma pack( push, 
4  )

__declspec( align(
32) )struct
 D
{
    
int
 i1;
    
double
 d1;
    
int
 i2;
    
int
 i3;
};

int
 main()
{
    cout 
<< "sizeof(int) = "<<sizeof(int<<
 endl;
    cout 
<< "sizeof(char) = " << sizeof(char<<
 endl;
    cout 
<< "sizeof(double) = " << sizeof(double<<
 endl;
    cout 
<< sizeof(D) <<
 endl;
    system(
"PAUSE"
);
    
return 0
;
}

 

这段代码在VS 2010中的运行结果是,sizeof(D)的大小为32,而在Dev C++C-Free 5.0以及gcc中的结果都似乎20。下面我们来着重讲讲关于__declspec( align(#) )的用法:

正如前面所说的,当有__declspec( align(#) )pack的时候,__declspec( align(#) )的优先级要高些。所以对于上面这个例子,我们首先来计算出来每一个的大小。

 

1.       成员的地址如何取?

规则:成员的地址要取pack(n)__declspec( align(m) ),以及成员自身大小这三者之间的最小值,也就是,min(n,m,sizeof(成员变量类型)),那么我们可以对每一个结构体的成员都进行分析。

 

第一个为int类型,占据4B,所以地址是[0~3].

第二个为double类型,它的地址要根据min(4,32,sizeof(double))来判断,所以应该是4的倍数,也就是相邻着int类型的i1存放。地址是[4~11]

第三个为int类型,占据4B,同样应该是4的倍数,地址是[12~15].

第四个为int类型,占据4B,地址为[16~19].

 

从而总的地址是从[0~19]连续存放的20个字节,那么是否sizeof(D)的大小就是20呢?

 

经过测试,我们可以看到,在VS 2010中,结果是32why

 

这就要用__declspec( align(#) )来解释了。也就是下面第二点的内容。

 

2.       结构体最后的大小如何决定?

规则:结构体最后的大小与__declspec( align(m) )有关,其要么是它的倍数,要么是结构体中最大偏移量的倍数,取最大

 

根据这个规则,这里align32,而结构体中最大的是double类型,也就是应该是max(32,8)=32,所以最后结构体的大小应该是32的倍数,而明显上面我们看到的实际大小是20B,从而需要扩展到32B

 

在这里,就体现了__declspec( align(m) )的强大作用!

 

同样的,为了体现该语句的作用,我们去掉这个语句,运用我们前面一节内容的知识,来计算并测试sizeof(D),最终不论是在VS 2010还是Dev C++中,运行的结果都是上面我们所预测的20B

 

OK,下面回到最后的疑问,也就是前面我们提出的,为何加入了__declspec( align(m) )语句之后,在DevC++VS 2010的结果不同?

 

实际上,对于这些内存对齐的处理,不同的编译器可能采取不同的处理,就像前面一节中所说的,我将pack误用为package,导致根本没有达到按照我要求的字节对齐的目的,而且编译器根本不提供任何警告信息。那么,这里合理的解释是:Dev C++不支持这种用法。

 

通过查阅资料,参照这篇文章【 SSE指令介绍及其CC++应用 】(http://blog.csdn.net/delphihero/archive/2006/09/24/1270069.aspx),我们可以看到作者有这么一段话:

 

接下来我举一个例子来说明SSE的指令函数是如何使用的,必须要说明的是我以下的代码都是在VC7.1的平台上写的,不保证对其它如Dev-C++Borland C++等开发平台的完全兼容。

 

这里要注意一下,我使用了__declspec(align(16))做为数组定义的修释符,这表示该数组是以16字节为边界对齐的,因为SSE指令只能支持这种格式的内存数据。

  我们在这里看到了SSE算的强大,相信它会成为多媒体程序员手中用来对付无穷尽流媒体数据的一把利剑。我后面还会写一些关于SSE算法更复杂应用的文章,敬请关注,感谢您抽时间阅读!

 

从这篇文章我们可以看到,SSE指令集的情况下,在VC 7.1下才支持__declspec(align(16))这种用法,而对于其他平台不一定有效。而前面我们使用的Dev C++以及C-Free,都是基于g++或者MinGW,不一定会支持这种方式,或者说,不一定按照这种内存对齐的建议来做,也就造成了结果的不同。

 

 

下面我们来继续探讨结构体中有结构体的情况。

 

先看看下面这段代码:

 

#include <stdio.h>

#include 
"stdafx.h"
#include 
<stdlib.h>
//using namespace std;

#pragma pack( push, 
4  )

__declspec( align(
32) )struct
 D
{
    
int
 i1;
    
double
 d1;
    
int
 i2;
    
int
 i3;
};

__declspec( align(
16) ) struct
 E
{
     
int
 i1;
     D m_d;
     
int
 i2;
};

int
 main()
{
    cout 
<< "sizeof(int) = "<<sizeof(int<<
 endl;
    cout 
<< "sizeof(char) = " << sizeof(char<<
 endl;
    cout 
<< "sizeof(double) = " << sizeof(double<<
 endl;
    cout 
<< sizeof(D) <<
 endl;
    cout 
<< sizeof(E) <<
 endl;
    system(
"PAUSE"
);
    
return 0
;
}

 

最后运行的结果是sizeof(E)96,为何会是这个结果呢?我们来详细讲解下。

 

对于结构体E,第一个元素为int类型,所以占据[0~3]地址单元。

第二个元素是一个结构体,该结构体由于受上面__declspecalign(32) )的影响,优先级高,所以起始地址是32的倍数,而且大小为32B,从而应该放置在[32~63]单元处。

最后一个是int类型的变量,大小为4,所以应该是4的倍数,地址为[64~67]

 

故结构体E的大小应该是从[0~67],占据68B,而由于前面还有限制__declspecalign(16) ),同时成员变量的最大偏移是sizeof(D)=32,所以我们最后这个结构体的大小应该是他们中最大值的倍数,也就是32的倍数,68向上取32的倍数应该是96.故结果为96.

 

最后仍然是上面平台的问题,在Dev C++G++下面的结果不同,原因上面解释了。


MSDN:

“The sizeof value for any structure is the offset of the final member, plus that member's size, rounded up to the nearest multiple of the largest member alignment value or the whole structure alignment value, whichever is greater.”

中文:

“sizeof的结果都是结构体中最后的一个成员变量加上它的大小,再加上一个填充容量(padding),这个填充大小是成员变量最大的一个对齐参数或整个结构体的对齐参数的倍数,取哪个决定于哪个对齐参数较大”

 

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/e4209cbb-5437-4b53-b3fe-ac264501d404.htm

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/9cb63f58-658b-4425-ac47-af8eabfc5878.htm




P.S.:上面是关于内存对齐的研究,如有谬误,欢迎指出!


附参考资料和拓展:

1. #pragma pack :http://blog.sina.com.cn/s/blog_492aa57901008y3h.html 
2. #pragma pack( n )和__declspec( align(#) ) 的偏移量计算方法: http://blog.csdn.net/whoismickey/archive/2009/03/28/4032155.aspx
3. #pragma pack(push,1) (pop) :http://blog.csdn.net/jiang1013nan/archive/2009/11/25/4861248.aspx
4. 关于pragma pack的用法(四) C++中的内存对齐问题: http://www.cppblog.com/xczhang/archive/2007/12/23/39396.html
5. SSE指令介绍及其C、C++应用:http://blog.csdn.net/delphihero/archive/2006/09/24/1270069.aspx
6. c++中__declspec用法总结: http://sealbird.javaeye.com/blog/855096

 

 

http://hi.baidu.com/hehonglei123/blog/item/668a6f30e45e2fb55fdf0e69.html

 

 

一、背景

首先我想对“对齐”这个名词做一个自己的理解,我认为对齐不是从静态意义上来说的,而是从动态意义上来说的,是相对于CPU读取过程来说的。所谓对齐就是CPU读取某个变量时,使CPU读取的次数最少的内存排列方式。比如就拿32位(4字节)系统来说,long类型的数据CPU读取一次就能完成读取的,说明此数据是对齐的,如果读取两次才读取完成,说明是未对齐的;double数据类型的数据CPU读取两次就能完成读取的,说明是对齐的,如果读取三次或三次以上的为未对齐。

内存对齐你可能都没有听说过,这很正常,因为他对于使用高级语言(cc++javaobject-cc#)编程的人来说直接接触到的并不多。内存对齐的工作会由你所使用的编译器来做,所以这个工作对于大多数人来说十分的陌生,其实它时时刻刻都在我们身边。你点击开发工具的编译按钮时,此时其实你已经在使用它了。

虽然这个工作编译器会帮我们完成,但我们了解它的实现方式还是有必要的,这样我们就能更好的组织我们的数据结构,从而减少内存使用,提高运行效率。

内存对齐产生的原因总结起来就两种:

1、平台原因(移植原因):不是所有硬件平台都能访问任意地址上的任意数据的;有些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做出两次内存访问;而对齐的内存访问仅需要一次便可完成。

下面的例子是从性能上来考虑:

通俗的讲就是CPU读取内存不是连续读取的,而是分成块读取的,块的大小只能是124816(字节数)。下面举个例子:

0 1 2 3 4 5 6 7:这几个数字代表连续的内存地址,0代表内存开始地址。CPU(假设块大小为4)在读取此内存时是分两次读取的,第一次读取0-3,第二次读取4-7。我们现在有一个int类型的数据放在此处,分两种情况,第一种情况,数据的开始地址为0;第二种情况,数据的开始地址为2

第一种情况:CPU只需读取一次内存便可得到整数的值。

第二种情况:CPU需要读取两次内存才能得到整数的值,第一次的2-3和第二次的4-5组合成一个整数。

第二种情况会大大降低了CPU的性能。在数据量小的时候,你可能感觉不到,如果数据量十分庞大,你就会感觉出来。这里的这个例子主要是在讲性能问题,对于平台原因并未提及。

二、对齐规则:

每个平台上的编译器都有自己默认的“对齐系数”(也叫对齐模数)。这个系数程序员可以通过编译命令“#pragma pack(n)”改变,其中n的取值必须是以下数字中的一个:124816

我先做一个自定义变量:

minVarLen = minpack指定长度,结构体(联合体)当前成员变量数据长度);

minStructLen = minpack指定长度,结构体(联合体)最大数据长度);

规则:

1、数据成员对齐规则:对于结构体(struct)或者联合体(union)中的成员,第一个成员的offset0;以后每个成员变量的对齐按照minVarLen进行。

2、结构体(联合体)本身对齐规则:在数据成员完成各自对齐的情况下,结构体(联合体)本身也要对齐,对齐将按照minStructLen进行。

3、综合:结合12所述,如果pack指定的长度大于或等于结构体(联合体)最大数据成员长度时,pack为无效。

我并不喜欢上面的三条解释,看完后总是很晕(哈哈),相比较而言,我更喜欢下面的解释:

1、数据成员对齐规则:结构体(联合体)的第一个成员的offset0,以后每个成员变量的offset值必须是minVarLen的倍数。

2、结构体(联合体)本身对齐规则:在1的基础上,结构体(联合体)本身也要实现对齐,结构体(联合体)的内存地址必须是minStructLen的倍数。

3、整体:内存中结构体(联合体)的整体大小为minStructLen整数倍。

 

三、例子:

上面我们讲了内存对齐的背景及规则,下面我们用一个例子来验证我们的理论是否正确:

#pragma pack(4)

Struet A{

          Char c1

          Double d

          Char c2

          Short s

          Int i

}

#pragma pack

 

void main(){

          struct A  struA;

          int yushu1 = (int)&struA % (min(4, sizeof(double));

          int yushu2 = (int)&struA % sizeof(double);

          int yushu3 =( (int)&struA.d – (int)&struA.c1)) % (min(4, sizeof(double));

          int yushu4=( (int)&struA.c2 – (int)&struA.d)) % (min(4, sizeof(char));

int yushu5=( (int)&struA.s – (int)&struA.c2)) % (min(4, sizeof(short));

int yushu6=( (int)&struA.i – (int)&struA.s)) % (min(4, sizeof(int));

int yushu7 = (int)sizeof(Struet A) % minStructLen;

}

运算结果为(以下结果为运行多次的结果,并不是一次,只是多次运行的结果都是一样的):

yushu1 = 0;

yushu2 = 4;

yushu3 = 0;

yushu4 = 0;

yushu5 = 0;

yushu6 = 0;

yushu7 = 0;

结果分析:

yushu1yushu2:说明规则1是正确的。

yushu3-6:说明规则1是正确的。

Yushu7:说明规则3是正确的,结构体的尺寸为minStructLen整数倍。

这个例子只是为了验证我们的理论是正确的,就不再举其它例子了,如果感兴趣可以自己在工程中试验一些小例子,看看试验结果与理论值是否相符合。

 

 

http://wenku.baidu.com/view/6b2e216c1eb91a37f1115c69.html

 

 

http://www.189works.com/article-10322-1.html

 

 

一、什么是对齐,以及为什么要对齐:
  1. 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
  2. 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低 字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。
  二、对齐的实现
  通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。
  但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。
  对齐的算法:
  由于各个平台和编译器的不同,现以本人使用的gcc version 3.2.2编译器(32位x86平台)为例子,来讨论编译器对struct数据结构中的各成员如何进行对齐的。
  设结构体如下定义:
  1 <SPAN>struct A {
  2     int a;
  3     char b;
  4     short c;
  5 };
  6 </SPAN>
  结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。所以A用到的空间应该是7字节。但是因为编译器要对数据成员在空间上进行对齐。
  所以使用sizeof(strcut A)值为8。
  现在把该结构体调整成员变量的顺序。
  1 struct B {
  2     char b;
  3     int a;
  4     short c;
  5 };
  这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12。
  下面我们使用预编译指令#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。
  1 #progma pack (2) /*指定按2字节对齐*/
  2 struct C {
  3     char b;
  4     int a;
  5     short c;
  6 };
  7 #progma pack () /*取消指定对齐,恢复缺省对齐*/
  8 //sizeof(struct C)值是8。
  修改对齐值为1:
  1 #progma pack (1) /*指定按1字节对齐*/
  2 struct D {
  3     char b;
  4     int a;
  5     short c;
  6 };
  7 #progma pack () /*取消指定对齐,恢复缺省对齐*/
  8 sizeof(struct D)值为7。
  对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
  这里面有四个概念值:
  1)数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。
  2)指定对齐值:#pragma pack (value)时的指定对齐值value。
  3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
  4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
  有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是 数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整 数倍,结合下面例子理解)。这样就不难理解上面的几个例子的值了。
  例子分析:
  分析例子B;
  1 struct B {
  2     char b;
  3     int a;
  4     short c;
  5 };
  假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指 定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为 4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐 值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存 放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B 共有12个字节,sizeof(struct B)=12;
  同理,分析上面例子C:
  1 <SPAN>#pragma pack (2) /*指定按2字节对齐*/
  2 struct C {
  3     char b;
  4     int a;
  5     short c;
  6 };
  7 #pragma pack () /*取消指定对齐,恢复缺省对齐*/
  8 </SPAN>
  第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么 b存放在0x0000,符合0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续 字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
  在0x0006、0x0007中,符合 0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以 C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.
  有 了以上的解释,相信你对C语言的字节对齐概念应该有了清楚的认识了吧。在网络程序中,掌握这个概念可是很重要的喔,在不同平台之间(比如在Windows 和Linux之间)传递2进制流(比如结构体),那么在这两个平台间必须要定义相同的对齐方式,不然莫名其妙的出了一些错,可是很难排查的

 

 

http://baike.baidu.com/view/4786260.htm

 

 

在最近的项目中,我们涉及到了“内存对齐”技术。对于大部分程序员来说,“内存对齐”对他们来说都应该是“透明的”。“内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。如果你想了解更加底层的秘密,“内存对齐”对你就不应该再透明了。

  一、内存对齐的原因

[zz]内存对齐_第1张图片

内存对齐(3张)

 

  大部分的参考资料都是如是说的:

  1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

  二、对齐规则

  每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

  规则:

  1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

  2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

  3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

  Win32平台下的微软C编译器(cl.exefor 80×86)的对齐策略:

  1) 结构体变量的首地址是其最长基本类型成员的整数倍;

  备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能是该基本数据类型的整倍的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。

  2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

  备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

  3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。

  备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

  4) 结构体内类型相同的连续元素将在连续的空间内,和数组一样。

  5) 如果结构体内存在长度大于处理器位数的元素,那么就以处理器的倍数为对齐单位;否则,如果结构体内的元素的长度都小于处理器的倍数的时候,便以结构体里面最长的数据元素为对齐单位。

  三、试验

  我们通过一系列例子的详细说明来证明这个规则吧!

  我试验用的编译器包括GCC 3.4.2和VC6.0的C编译器,平台为Windows XP + Sp2。

  我们将用典型的struct对齐来说明。首先我们定义一个struct:

  #pragma pack(n) /* n = 1, 2, 4, 8, 16 */

  struct test_t {

  int a;

  char b;

  short c;

  char d[6];

  };

  #pragma pack(n)

  首先我们首先确认在试验平台上的各个类型的size,经验证两个编译器的输出均为:

  sizeof(char) = 1

  sizeof(short) = 2

  sizeof(int) = 4

  我们的试验过程如下:通过#pragma pack(n)改变“对齐系数”,然后察看sizeof(struct test_t)的值。

  1、1字节对齐(#pragma pack(1))

  输出结果:sizeof(struct test_t) = 13 [两个编译器输出一致]

  分析过程:

  1) 成员数据对齐

  #pragma pack(1)

  struct test_t {

  int a; /* int型,长度4 > 1 按1对齐;起始offset=0 0%1=0;存放位置区间[0,3] */

  char b; /* char型,长度1 = 1 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

  short c; /* short型,长度2 > 1 按1对齐;起始offset=5 5%1=0;存放位置区间[5,6] */

  char d[6]; /* char型,长度1 = 1 按1对齐;起始offset=7 7%1=0;存放位置区间[7,C] */

  };/*char d[6]要看成6个char型变量*/

  #pragma pack()

  成员总大小=13

  2) 整体对齐

  整体对齐系数 = min((max(int,short,char), 1) = 1

  整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 13 /*13%1=0*/ [注1]

  2、2字节对齐(#pragma pack(2))

  输出结果:sizeof(struct test_t) = 14 [两个编译器输出一致]

  分析过程:

  1) 成员数据对齐

  #pragma pack(2)

  struct test_t {

  int a; /* int型,长度4 > 2 按2对齐;起始offset=0 0%2=0;存放位置区间[0,3] */

  char b; /* char型,长度1 < 2 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

  short c; /* short型,长度2 = 2 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

  char d[6]; /* char型,长度1 < 2 按1对齐;起始offset=8 8%1=0;存放位置区间[8,D] */

  };

  #pragma pack()

  成员总大小=14

  2) 整体对齐

  整体对齐系数 = min((max(int,short,char), 2) = 2

  整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 14 /* 14%2=0 */

  3、4字节对齐(#pragma pack(4))

  输出结果:sizeof(struct test_t) = 16 [两个编译器输出一致]

  分析过程:

  1) 成员数据对齐

  #pragma pack(4)

  struct test_t {

  int a; /* int型,长度4 = 4 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */

  char b; /* char型,长度1 < 4 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

  short c; /*short型, 长度2 < 4 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

  char d[6]; /* char型,长度1 < 4 按1对齐;起始offset=8 8%1=0;存放位置区间[8,D] */

  };

  #pragma pack()

  成员总大小=14

  2) 整体对齐

  整体对齐系数 = min((max(int,short,char), 4) = 4

  整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 16 /*16%4=0*/

  4、8字节对齐(#pragma pack(8))

  输出结果:sizeof(struct test_t) = 16 [两个编译器输出一致]

  分析过程:

  1) 成员数据对齐

  #pragma pack(8)

  struct test_t {

  int a; /* int型,长度4 < 8 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */

  char b; /* char型,长度1 < 8 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

  short c; /* short型,长度2 < 8 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

  char d[6]; /* char型,长度1 < 8 按1对齐;起始offset=8 8%1=0;存放位置区间[8,D] */

  };

  #pragma pack()

  成员总大小=14

  2) 整体对齐

  整体对齐系数 = min((max(int,short,char), 8) = 4

  整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 16 /*16%4=0*/

  5、16字节对齐(#pragma pack(16))

  输出结果:sizeof(struct test_t) = 16 [两个编译器输出一致]

  分析过程:

  1) 成员数据对齐

  #pragma pack(16)

  struct test_t {

  int a; /* int型,长度4 < 16 按4对齐;起始offset=0 0%4=0;存放位置区间[0,3] */

  char b; /* char型,长度1 < 16 按1对齐;起始offset=4 4%1=0;存放位置区间[4] */

  short c; /* short型,长度2 < 16 按2对齐;起始offset=6 6%2=0;存放位置区间[6,7] */

  char d[6]; /* char型,长度1 < 16 按1对齐;起始offset=8 8%1=0;存放位置区间[8,D] */

  };

  #pragma pack()

  成员总大小=14

  2) 整体对齐

  整体对齐系数 = min((max(int,short,char), 16) = 4

  整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 16 /*16%4=0*/

  四、结论

  8字节和16字节对齐试验证明了“规则”的第3点:“当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果”。另外内存对齐是个很复杂的东西,读者不妨把上述结构体中加个double型成员进去练习一下,上面所说的在有些时候也可能不正确。呵呵^_^

  [注1]

  什么是“圆整”?

  举例说明:如上面的8字节对齐中的“整体对齐”,整体大小=9 按 4 圆整 = 12

  圆整的过程:从9开始每次加一,看是否能被4整除,这里9,10,11均不能被4整除,到12时可以,则圆整结束。

你可能感兴趣的:(数据结构,c,struct,平台,编译器,alignment)