陈式太极拳108式:内存对齐

来源:百度文库 编辑:偶看新闻 时间:2024/04/27 22:04:48
内存对齐

内存地址的对齐主要考虑三个因素:

      1:对于每个成员的起始地址是他本身所占的整数倍

      2:整个所占的内存是成员中占的地址内存最多的整数倍

      3:有#pragma pack(int)进行设置,如果结构体某成员的sizeof大于你设置的,则按你的设置来对齐

系统默认是#pragma pack(4)
从计算结构变量字节大小的问题开始
问题:
     
在32位编译系统中,
       typedef struct
       {
            int  A   0-3
            char B  4***//后面填充是为了满足第二条规则4的整数倍。
        }T_s;
        请问T_s的长度为几个字节?
答案:
     
题目不够严谨,跟编译器和编译选项有关的。
        pc上的32位编译器一般缺省是4位对齐,所以长度会是8,此时在B后填充3字节,但对齐方式可以改的;
        而有些嵌入系统的编译器缺省是不对齐的,所以长度为5。
比如在vc中,
        如果
        #pragma pack(4)     //缺省
        则sizeof(T_s)等于8
        如果
        #pragma pack
        则sizeof(T_s)等于5
        而且和cpu有关,在有的机器上int不一定就是32位
      
要因结构对齐而定,对齐方式可以是1, 2, 4, 8, or 16
        1对齐:5;2对齐:6;4对齐:8;8对齐:8;16对齐:8
    
char 是8位的,结构长度是8字节,在B之前没有对齐被位,但在B之后要补3个字节以便于在数组中把下一个元素的A对齐到4字节边界,当然这都是一般编 译器在4字节对齐的情况下,如果某一编译器偏不这样实现,你也不能说它错了。因此如果我写一个编译器就把它的长度设为6字节,那么说它是6字节也正确。其 实字节对齐的知识对编写代码并没有什么帮助,并且也不应该利用这些知识。我以前曾经说过正确的态度是“不假设没有进行字节对齐,不假设编译时的对齐方式 (包括可以不进行对齐)”。

    
计算结构变量的大小就必须讨论数据对齐问题。为了CPU存取的速度最快(这同CPU取数操作有关,详细的介绍可以参考一些计算机原理方面的书),C++在处理数据时经常把结构变量中的成员的大小按照4或8的倍数计算,这就叫数据对齐(data alignment)。这样做可能会浪费一些内存,但理论上速度快了。当然这样的设置会在读写一些别的应用程序生成的数据文件或交换数据时带来不便。MS VC++中的对齐设定,有时候sizeof得到的与实际不等。一般在VC++中加上#pragma pack(n)的设定即可.或者如果要按字节存储,而不进行数据对齐,可以在Options对话框中修改Advanced compiler页中的Data alignment为按字节对齐。

看一个例子:
将A写入二进制文件
#pragma pack (1)
struct A
{
char a;
int b;
};
文件
61 06 00 00 00
#pragma pack (2)
struct A
{
char a;
int b;
};
文件
61 CC 06 00 00 00
#pragma pack (4)
struct A
{
char a;
int b;
};
文件
61 CC CC CC 06 00 00 00
#pragma pack (4)
struct A
{
char a;
int b;
};
文件
61 CC CC CC 06 00 00 00
#pragma pack (8)
struct A
{
char a;
int b;
};
文件
61 CC CC CC 06 00 00 00
该例子作者得出的结论:
结论是 实际的对齐长度 = [pack指定的对齐长度]和[struct中最长成员的长度]较小的一个。
[C] 结构对齐
http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=182474



        在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间;各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间

例如,下面的结构各成员空间分配情况

struct  test
{
char  x1;
short  x2;
float  x3;
char  x4;
};
                 
  结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之 间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求 4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空 间为12字节。

更改C编译器的缺省分配策略
  一般地,可以通过下面的两种方法改变缺省的对界条件:
  ·  使用伪指令#pragma  pack  ([n])
  ·  在编译时使用命令行参数
#pragma  pack  ([n])伪指令允许你选择编译器为数据分配空间所采取的对界策略:

    
例如,在使用了#pragma  pack  (1)伪指令后,test结构各成员的空间分配情况就是按照一个字节对齐了

#pragma  pack(push)  //保存对齐状态
#pragma  pack(1) 
#pragma  pack(pop)

编译器默认都是8字节对齐;


=============================================================================

http://data.gameres.com/message.asp?TopicID=13636

什么是内存对齐
    考虑下面的结构:
         struct foo
         {
           char c1;
           short s;
           char c2;
           int i;
          };
  
    假设这个结构的成员在内存中是紧凑排列的,假设c1的地址是0,那么s的地址就应该是1,c2的地址就是3,i的地址就是4。也就是
    c1 00000000, s 00000001, c2 00000003, i 00000004。
    可是,我们在Visual c/c++ 6中写一个简单的程序:
         struct foo a;
    printf("c1 %p, s %p, c2 %p, i %p\n",
        (unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
        (unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
        (unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
        (unsigned int)(void*)&a.i - (unsigned int)(void*)&a);
    运行,输出:
         c1 00000000, s 00000002, c2 00000004, i 00000008。
    为什么会这样?这就是内存对齐而导致的问题。
为什么会有内存对齐
    以下内容节选自《Intel Architecture 32 Manual》。
    字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
    无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
    一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。
    某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16 整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。
编译器对内存对齐的处理
    缺省情况下,c/c++编译器默认将结构、栈中的成员数据进行内存对齐。因此,上面的程序输出就变成了:
c1 00000000, s 00000002, c2 00000004, i 00000008。
编译器将未对齐的成员向后移,将每一个都成员对齐到自然边界上,从而也导致了整个结构的尺寸变大。尽管会牺牲一点空间(成员之间有空洞),但提高了性能。
也正是这个原因,我们不可以断言sizeof(foo) == 8。在这个例子中,sizeof(foo) == 12。
如何避免内存对齐的影响
    那么,能不能既达到提高性能的目的,又能节约一点空间呢?有一点小技巧可以使用。比如我们可以将上面的结构改成:
struct bar
{
    char c1;
    char c2;
    short s;
    int i;
};
    这样一来,每个成员都对齐在其自然边界上,从而避免了编译器自动对齐。在这个例子中,sizeof(bar) == 8。
    这个技巧有一个重要的作用,尤其是这个结构作为API的一部分提供给第三方开发使用的时候。第三方开发者可能将编译器的默认对齐选项改变,从而造成这个结构在你的发行的DLL中使用某种对齐方式,而在第三方开发者哪里却使用另外一种对齐方式。这将会导致重大问题。
    比如,foo结构,我们的DLL使用默认对齐选项,对齐为
c1 00000000, s 00000002, c2 00000004, i 00000008,同时sizeof(foo) == 12。
而第三方将对齐选项关闭,导致
    c1 00000000, s 00000001, c2 00000003, i 00000004,同时sizeof(foo) == 8。
如何使用c/c++中的对齐选项
    vc6中的编译选项有 /Zp[1|2|4|8|16] ,/Zp1表示以1字节边界对齐,相应的,/Zpn表示以n字节边界对齐。n字节边界对齐的意思是说,一个成员的地址必须安排在成员的尺寸的整数倍地址上或者是n的整数倍地址上,取它们中的最小值。也就是:
    min ( sizeof ( member ),  n)
    实际上,1字节边界对齐也就表示了结构成员之间没有空洞。
    /Zpn选项是应用于整个工程的,影响所有的参与编译的结构。
    要使用这个选项,可以在vc6中打开工程属性页,c/c++页,选择Code Generation分类,在Struct member alignment可以选择。
    要专门针对某些结构定义使用对齐选项,可以使用#pragma pack编译指令。指令语法如下:
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n  )
    意义和/Zpn选项相同。比如:
#pragma pack(1)
struct foo_pack
{
    char c1;
    short s;
    char c2;
    int i;
};
#pragma pack()
栈内存对齐
    我们可以观察到,在vc6中栈的对齐方式不受结构成员对齐选项的影响。(本来就是两码事)。它总是保持对齐,而且对齐在4字节边界上。
验证代码
#include
struct foo
{
    char c1;
    short s;
    char c2;
    int i;
};
struct bar
{
    char c1;
    char c2;
    short s;
    int i;
};
#pragma pack(1)
struct foo_pack
{
    char c1;
    short s;
    char c2;
    int i;
};
#pragma pack()
int main(int argc, char* argv[])
{
    char c1;
    short s;
    char c2;
    int i;
    struct foo a;
    struct bar b;
    struct foo_pack p;
    printf("stack c1 %p, s %p, c2 %p, i %p\n",
        (unsigned int)(void*)&c1 - (unsigned int)(void*)&i,
        (unsigned int)(void*)&s - (unsigned int)(void*)&i,
        (unsigned int)(void*)&c2 - (unsigned int)(void*)&i,
        (unsigned int)(void*)&i - (unsigned int)(void*)&i);
    printf("struct foo c1 %p, s %p, c2 %p, i %p\n",
        (unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
        (unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
        (unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
        (unsigned int)(void*)&a.i - (unsigned int)(void*)&a);
    printf("struct bar c1 %p, c2 %p, s %p, i %p\n",
        (unsigned int)(void*)&b.c1 - (unsigned int)(void*)&b,
        (unsigned int)(void*)&b.c2 - (unsigned int)(void*)&b,
        (unsigned int)(void*)&b.s - (unsigned int)(void*)&b,
        (unsigned int)(void*)&b.i - (unsigned int)(void*)&b);
    printf("struct foo_pack c1 %p, s %p, c2 %p, i %p\n",
        (unsigned int)(void*)&p.c1 - (unsigned int)(void*)&p,
        (unsigned int)(void*)&p.s - (unsigned int)(void*)&p,
        (unsigned int)(void*)&p.c2 - (unsigned int)(void*)&p,
        (unsigned int)(void*)&p.i - (unsigned int)(void*)&p);
    printf("sizeof foo is %d\n", sizeof(foo));
    printf("sizeof bar is %d\n", sizeof(bar));
    printf("sizeof foo_pack is %d\n", sizeof(foo_pack));
  
    return 0;