C语言sizeof,pragma 和对齐详解(转贴精华加自创)

来源:互联网 发布:luac windows 编辑:程序博客网 时间:2024/05/23 01:20

1. 为什么要对齐? 以32位的CPU为例(16,64位同 ),它一次可以对一个32位的数进行运算,它的数据 总线的宽度是32位,它从内存中一次可以存取的最大数为32位,这个数叫CPU的字(wor d)长。 在进行硬件设计时,将存储体组织成32位宽,如每个存储体的宽度是8位,可用四块存 储体 与CPU的32位数据总线相连(这也是为什么以前的 386/486 计算机插SIMM30内存条(8位 )时,必 须同时插四条的原因),

请参见下图: 1   8    16       24       32

-------- ------- ------- -------- | long1 | long1 | long1 | long1 |

-------- ------- ------- -------- |            |             |             | long2 |

 -------- ------- ------- -------- | long2 | long2 | long2 |            |

 -------- ------- ------- -------- | ....

当一个long型数(如图中long1)在内存中的位置正好与内存的字边界对齐时,CPU存取 这个 数只需访问一次内存,而当一个long型数(如图中long2)在内存中的位置跨越字边界时 ,CPU存 取这个数就需多次访问内存,如 i960cx 访问这样的数需读内存三次(一个BYTE,一个 short, 一个BYTE,由CPU的微代码执行,对软件透明),所以在对齐方式下,CPU的运行效率明 显快多 了,这就是要对齐的原因。 一般在编译器生成代码时,都可以根据各种CPU类型,将变量进行对齐,包括结构(st ruct) 中的变量,变量与变量之间的空间叫padding,有时为了对齐在一个结构的最后也会填入 padding, 通常叫tail padding。但在实际的应用中,我们确实有不对齐的要求,如在编通讯程序 时,帧的结 构就不能对齐,否则会带来错误及麻烦。所以各编译器都提供了不对齐的选项,但由于 这是ANSI C 中未规定的内容,所以各厂家的实现都不一样。下面是我们常用编译器的实现。

2. 一般编译器实现对齐的方法 由于各厂家的实现不一样,这里涉及的内容只使用于Visual C++ 4.x,Borland C++ 5 .0、3.1 及pRism x86 1.8.7 (C languange),其他厂家可能略有不同。 每种基本数据类型都有它的自然对齐方式(Natural Alignment),Align的值与该数据 类型的 大小相等,见下表:  

Data Type   sizeof                           Natural Align (signed/unsigned)  

char      1    1  

short      2    2  

long      4    4    .    .    .

同时用户还可以指定一个Align值(使用编译开关或使用#pragma另外,还有如下的一种方式:
     · __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
     · __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。),当用户指定一个Al ign值 n (或编译器的缺省)时,每种数据类型的实际(当前)Align值定义如下:  Actual Align = min ( n, Natual Align ) //公式 1

如当用户指定Align值为 2 时,char 的实际Align值仍为 1,short及long的实际Align 值为 2。 当用户指定Align值为 1 时,所有类型的实际Align值都为 1。

复杂数据类型(Complex or Aggregate type,包括 array, struct 及 union)的对齐 值定义 如下: struct:结构的Align值等于该结构所有成员的 Actual Align 值中最大的一个 Align 值, 注意成员的Align值是它的实际Align值。

array: 数组的Align值等于该数组成员的 Actual Align 值

union: 联合的Align值等于该联合最大成员的 Actual Align 值 同时当用户指定一个Align值时,上面的公式 1 同样起作用,只不过Natual Align应理 解为 当前的Actual Align。

那么编译器是如何根据一个类型的Align值来分配存储空间(主要是在结构中的空间) 的呢?

有如下两个规律:  

1:一个结构成员的offset等于该成员Actual Align值的整数倍,如果凑不成整数倍, 就在其前加padding  

2:一个结构的大小等于该结构Actual Align值的整数倍,如果凑不成整数倍,就在其后加padding(tail padding)。一个结构的大小在其定义时就已确定,不会因为其 Actual Align值的改变而改变。

例如有如下两个结构定义:  

#pragma pack(8) //指定Align为 8  

struct STest1  {  

char ch1;  

long lo1;  

char ch2;  

} test1;  

#pragma pack()  

现在 Align of STest1 = 4 , sizeof STest1 = 12 ( 4 * 3 )

 

  #pragma pack(2) //指定Align为 2  

struct STest2  {  

char ch3;  

STest1  test;  

} test2;  

#pragma pack()  

现在 Align of STest1 = 2, Align of STest2 = 2 , sizeof STest2 = 14 (2 +12 )  ,请注意sizeof(Stest1)是12而非8.说明structure 的pack 值在其定义处起作用。

从以上可以看出,用户可以在任何需要的地方定义不同的align值。

下面主要讲一下#prag ma的 实现。

Visual C++ :VC使用 #pragma pack( [n] ),其中 n 可以是 1, 2, 4, 8, 16, 编译 器在  遇到一个#pragma pack(n)后就将 n 当作当前的用户指定aling值,直到另一个#prag ma  pack(n),当遇到一个不带 n 的 pack 时,就恢复以前使用的align值。 Borland C++:BC使用 #pragma option -an ,在 BC 5.0 的Online Help中没有发现对 #pragma pack的支持,但发现在其系统头文件中使用的都是#pragma pack。 pRism x86 : 使用 #pragma pack( [n] ) ,但奇怪的是 C 文件与 C++ 文件生成的代 码不一  样,有待进一步研究。 gcc960   : 使用 #pragma pack n 及 #pragma align n,两个开关的意义不一样,并 且相互  作用,比较复杂,但同时使用 #pragma pack 1 及 #pragma align 1 可以实现与  Visual C++中 #pragma pack(1) 一样的功能。 其他编译器的方法各不相同,可参见手册。如果要使用不同的编译器编译软件时,就要 针对不 同的编译器使用不同的预处理器指令。

4. 使用 #pragma pack (或其他开关)需注意的问题

1. 为了保证执行速度,尽量不使用#pragma pack;

2. 不同的编译器生成的代码极有可能不同,一定要查看相应手册,并做实验。

3. 需要加pack的地方一定要在定义结构的头文件中加,不要依赖命令行选项,因为如 果很多人    使用该头文件,并不是每个人都知道应该pack。特别是为别人开发库文件时,如果 一个库函    数使用了struct作为其参数,当调用者与库文件开发者使用不同的pack时,就会造 成错误,    而且该类错误很不好查。在VC及BC提供的头文件中,除了能正好对齐在四字节上的 结构外,    都加了pack,否则我们编的Windows程序哪一个也不会正常运行。

4. 在 #pragma pack(n) 后一定不要include其他头文件,若包含的头文件中改变了al ign值,    将产生非预期结果。    VC中提供了一种安全使用pack的方法:   #pragma pack( [ push | pop ], n )    #pragma pack( push, n )将当前的align值压入编译器的一个内部堆栈,并使用 n 作为当    前的align值,而#pragma pack(pop)则将内部堆栈中的栈顶值作为当前的align值, 这样就保证了嵌套pack时的正确。

5. 不要多人同时定义一个数据结构。在多人合作开发一个软件模块时,为了保持自己的编程风格,每个人都要对同一结构定义一份符合自己风格的数据类型,当两个人之间需 要传递    该数据结构时,如果两个人的 pack 值不一样,就会产生错误,该类错误也很难查 。

 

附两道测试题:

#pragma pack(8)

struct s1{
   short a;
   long b;
};

struct s2{
      char c;
      s1 d;
     long long e;
};

  struct s3{
      char c;
       short a;
   long b;
     long long e;
};


1.sizeof(s2) = ?sizeof(s3)=?
2.s2的c后面空了几个字节接着是d?

结果如下:

sizeof(S2)结果为24.

sizeof(S3)结果为16.

原创粉丝点击