精华内容
下载资源
问答
  • 内存对齐分配策略(含位域模式) 1:内存对齐定义:   现在使用的计算机中内存空间都是按照字节划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际上计算机系统对于基本数据类型...
    内存对齐分配策略(含位域模式)

    1:内存对齐定义: 
        现在使用的计算机中内存空间都是按照字节划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际上计算机系统对于基本数据类型在内存中的存放位置都有限制,要求这些数据存储首地址是某个数K的倍数,这样各种基本数据类型在内存冲就是按照一定的规则排列的,而不是一个紧挨着一个排放,这就是内存对齐。

    对齐模数: 
        内存对齐中指定的对齐数值K成为对齐模数(Alignment Modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。

    2:内存对齐的好处: 
        内存对齐作为一种强制的要求,第一简化了处理器与内存之间传输系统的设计,第二可以提升读取数据的速度。各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。 
        Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。但是如果想提升性能,应该注意内存对齐方式。 
    ANSI C标准中并没有规定相邻声明的变量在内存中一定要相邻。为了程序的高效性,内存对齐问题由编译器自行灵活处理,这样导致相邻的变量之间可能会有一些填充字节。对于基本数据类型(int char等),他们占用的内存空间在一个确定硬件系统下有确定的值。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。

    3:内存对齐策略: 
    微软C编译器(cl.exe for 80×86)的对齐策略: 
    第一: 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 
    备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。 
    第二: 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 
    备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 
    第三: 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 
    备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

    填充字节就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。对于结构体本身也存在着对齐要求,ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,但是可以更严格(但此非强制要求,VC7.1就仅仅是让它们一样严格)。C标准保证,任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说,数组各元素之间不会有空隙。

    总结规则如下: 
    0: 结构体变量的首地址能够被其最宽基本类型成员的大小所整除 
    1: VC6和VC71默认的内存对齐方式是 #pragam pack(8) 
    2: 结构体中每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐. 
    3:   结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍. 
    4:   结构体本身也存在着对齐要求规则,不能比它所有字段中要求最严格的那个宽松. 
    5: 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。 
    6: 在GCC中,对齐模数的准则是:对齐模数最大只能是 4,也就是说,即使结构体中有double类型,对齐模数还是4,所以对齐模数只能是1,2,4。 
          而且在上述的规则中,第3条里,offset必须是成员大小的整数倍: 
           (1): 如果这个成员大小小于等于4则按照上述准则是可行的, 
           (2): 如果成员的大小大于4,则结构体每个成员相对于结构体首地址的偏移量只能按照是4的整数倍来进行判断是否添加填充。

    例子1(VC8): 
    typedef struct ms1 { 
      char a; 
      int b; 
    } MS1;

    typedef struct ms2 { 
      int a; 
      char b; 
    } MS2; 
    MS1中有最强对齐要求的是b字段(int类型),字段a相对于首地址偏移量为0(1的倍数),直接存放,此时如果直接存放字段b,则字段b相对于结构体变量首地址的偏移量为1(不是4的倍数),填充3字节,b由偏移地址为4开始存放。也就是遵循了第2条与第3条规则,而对于结构体变量本身,根据规则4,对齐参数至少应该为4。根据规则5,sizeof(MS1) = 8; 同样MS2分析得到的结果也是如此。

    例子2VC8: 
    typedef struct ms3 { 
      char a; 
      short b; 
      double c; 
    } MS3;

    typedef struct ms4 { 
      char a; 
      MS3 b; 
    } MS4; 
    MS3中内存要求最严格的字段是c(8字节),MS3的对齐参数也是8字节; 那么MS4类型数据的对齐模数就与MS3中的double一致(为8),a字段后面应填充7个字节.sizeof(MS3) = 16; sizeof(MS4) = 24; 
    注意规则5中是说,结构体的总大小为结构体最宽基本类型成员大小的整数倍。注意是基本类型,这里的MS3不是基本类型。 
    对齐模数的选择只能是根据基本数据类型,所以对于结构体中嵌套结构体,只能考虑其拆分的基本数据类型。

    例子3(GCC): 
    struct T { 
      char ch; 
      double d ; 
    }; 
    在GCC下,sizeof(T)应该等于12个字节。VC8下为16字节。 
    ch为1字节,没有问题的,之后d的大小大于4,对于d的对齐模数只能是4,相对于结构体变量的首地址偏移量也只能是4,而不能使8的整数倍,由偏移量4开始存放,结构体共占12字节。 
    这里并没有执行第5条规则。

    位域情况: 
    C99规定int、unsigned   int和bool可以作为位域类型。但编译器几乎都对此作了扩展,允许其它类型类型的存在。 
    如果结构体中含有位域(bit-field),总结规则如下 
    1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止; 
    2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 
    3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式; 
    4)如果位域字段之间穿插着非位域字段,则不进行压缩; 
    5) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。 
    备注:当两字段类型不一样的时候,对于不压缩方式,例如:

    struct N { 
      char c:2; 
      int i:4; 
    }; 
    依然要满足不含位域结构体内存对齐准则第3条,i成员相对于结构体首地址的偏移应该是4的整数倍,所以c成员后要填充3个字节,然后再开辟4个字节的空间作为int型,其中4位用来存放i,所以上面结构体在VC中所占空间为8个字节; 
    而对于采用压缩方式的编译器来说,遵循不含位域结构体内存对齐准则第3条,不同的是,如果填充的3个字节能容纳后面成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间,所以上面结构体N在GCC或者Dev- C++中所占空间应该是4个字节。

    例子4: 
    typedef struct { 
      char c:2; 
      double i; 
      int c2:4; 
    }N3; 
    按照含位域规则4,在GCC下占据的空间为16字节,在VC下占据的空间是24个字节。

    结论: 
    -------- 
    定义结构体的时候,成员最好能从大到小来定义,那样能相对的省空间。例如如下定义: 
    struct A { 
      double d; 
      int i; 
      char c; 
    }; 
    那么,无论是windows下的vc系列编译器,还是linux下的gcc,都是16字节。

    例子5: 
    typedef union student{ 
      char name[10]; 
      long sno; 
      char sex; 
      float score [4]; 
    } STU; 
    STU aa[5]; 
    cout<<sizeof(aa)<<endl; 
    union是可变的以其成员中最大的成员作为该union的大小16*5=5=80

    例子6: 
    typedef struct student{ 
      char name[10]; 
      long sno; 
      char sex; 
      float score [4]; 
    } STU; 
    STU aa[5]; 
    cout<<sizeof(aa)<<endl; 
    STU占空间为:10字节(char)+空2字节+4字节(long)+1字节(char)+空3字节+16字节(float)=36字节,36*5=180字节

    例子7(VC8.0): 
    typedef struct bitstruct { 
      int b1:5; 
      int b2:2; 
      int b3:3; 
    }bitstruct;

    int _tmain(int argc, _TCHAR* argv[]) { 
      bitstruct b; 
      memcpy(&b,"EM",sizeof(b)); 
      cout<<sizeof(b)<<endl; 
      cout<<b.b1<<endl<<b.b2<<endl<<b.b3; 
      return 0; 

    对于bitstruct是含有位域的结构体,sizeof(int)为4字节,按照规则1、2,首先b1占起始的5个字节, 根据含位域规则1, b2紧跟存放,b3也是紧跟存放的。 
    根据规则5,得到sizeof(bitstruct) = 4。 
    现在主流的CPU,intel系列的是采用的little endian的格式存放数据,motorola系列的CPU采用的是big endian. 
    以主流的little endian分析: 
    在进行内存分配的时候,首先分配bitstruct的第一个成员类型int(4字节),这四个字节的存放按照低字节存储在低地址中的原则。 
    int共4个字节: 
    第4个字节 - 第3个字节 - 第2个字节 - 第1个字节,

    在内存中的存放方式如下所示。 
    而后为b1分配5位,这里优先分配的应该是低5位,也就是第一个字节的低5位。 
    继而分配b2的2个字节,也就是第1个字节中紧接着的2位。 
    最后分配b3的3位,按照规则1、2,b3还是紧接着存放的,b3的最低位是第一个字节的最高位,高两位为第2个字节的低两位。 
    内存分配图如下所示:

    QQ截图未命名 
    字符E二进制为0100 0101,字符M的二进制为0100 1101,在内存中存放如下所示:

    QQ截图未命名2 
    memcpy为按位拷贝的,所以两片内存区可以直接对应上,得到 
    b1的二进制形式为:00101 ,高位为0,正数,5 
    b2的二进制形式为:10 ,高位为1,负数,取反加1,添加符号,-2 
    b3的二进制形式为:b3的最低一位是0,高位为01,拼接后为010,正数,2

    内存分配情况感觉蛮奇怪的,按如下修改例7,b1应该为5,b2为-2,b3为-6,VC8.0下验证正确。 
    typedef struct bitstruct { 
      int b1:5; 
      int b2:2; 
      int b3:4; 
    }bitstruct;

    int _tmain(int argc, _TCHAR* argv[]) { 
      bitstruct b; 
      memcpy(&b,"EM",sizeof(b)); 
      cout<<sizeof(b)<<endl; 
      cout<<b.b1<<endl<<b.b2<<endl<<b.b3; 
      return 0; 
    }

    4: 定义数组时的内存布局及内存字节对齐

    int b=10;

    int a[3]={1,2,3};

    int c=11;

    image

    int b=0x01020304;

    char ch='a';

    对于一个数0x01020304; 对于一个数0x1122

    使用Little Endian方式时,低地址存放低字节,由低地址向高地址存放为:4->3->2->1 
    而使用Big Endian方式时, 低地址存放高字节,由低地址向高地址存放为:1->2->3->4

    而在Little Endian模式中,b的地址所指的就是 : 低地址(存放的是最低的字节)

    image

       1:  void __cdecl func_cdcel(int i, char *szTest) {
       2:      cout << "szTest在栈中的地址:" << &szTest << endl;
       3:      cout << "szTest本身的值(指向的地址):" << (void*)szTest << endl<<endl;
       4:      
       5:      cout << "i在堆栈中地址:" << &i << endl;
       6:      cout << "i的地址:" << &i << endl;
       7:   
       8:      int k,k2;
       9:      cout << "局部变量k的地址:" << &k << endl;
      10:      cout << "局部变量k2的地址:" << &k2 << endl;
      11:      cout << "-------------------------------------------------------" << endl;
      12:  }
      13:   
      14:  void __stdcall func_stdcall(int i, char *szTest){
      15:      cout << "szTest在栈中的地址:" << &szTest << endl;
      16:      cout << "szTest本身的值(指向的地址):" << (void*)szTest << endl<<endl;
      17:   
      18:      cout << "i在堆栈中地址:" << &i << endl;
      19:      cout << "i的地址:" << &i << endl;
      20:   
      21:      int k,k2;
      22:      cout << "局部变量k的地址:" << &k << endl;
      23:      cout << "局部变量k2的地址:" << &k2 << endl;
      24:      cout << "-------------------------------------------------------" << endl;
      25:  }
      26:   
      27:  int main(){
      28:      int a[4];
      29:      cout <<"a[0]地址:"<< &a[0] << endl;
      30:      cout <<"a[1]地址:"<< &a[1] << endl;
      31:      cout <<"a[2]地址:"<< &a[2] << endl;
      32:      cout <<"a[3]地址:"<< &a[3] << endl;
      33:   
      34:      int i = 0x22;
      35:      int j = 8;
      36:      char szTest[4] = {'a','b', 'c', 'd'};
      37:      cout <<"i的地址:"<<&i << endl;
      38:      cout <<"szTest的地址:"<<(void*)szTest << endl;
      39:      func_cdcel(i, szTest);
      40:      func_stdcall(i, szTest);
      41:  }

    输出为:

    a[0]地址:0012FF54 
    a[1]地址:0012FF58 
    a[2]地址:0012FF5C 
    a[3]地址:0012FF60                  <— 可见存储方式如上图所示,a[3]在高地址,先入栈,而数组地址a为a[0]的地址(低地址) 
    i的地址:0012FF48                    <— 这里进行了内存对齐,i的起始地址必定是i所占内存大小的倍数 
    szTest的地址:0012FF30  

    szTest在栈中的地址:0012FE5C   
    szTest本身的值(指向的地址):0012FF30

    i在堆栈中地址:0012FE58           <— i在堆栈中的地址低于szTest,也就是说szTest是先入栈的 
    i的地址:0012FE58 
    局部变量k的地址:0012FE48 
    局部变量k2的地址:0012FE3C 
    ------------------------------------------------------- 
    szTest在栈中的地址:0012FE5C 
    szTest本身的值(指向的地址):0012FF30

    i在堆栈中地址:0012FE58 
    i的地址:0012FE58 
    局部变量k的地址:0012FE48 
    局部变量k2的地址:0012FE3C 
    -------------------------------------------------------


    参考:http://www.cnblogs.com/ToDoToTry/archive/2011/09/05/2167694.html
    展开全文
  • 1:内存对齐定义:   现在使用的计算机中内存空间都是按照字节划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际上计算机系统对于基本数据类型在内存中的存放位置都有限制,要求这些...

    转载地址:http://www.cnblogs.com/alex-tech/archive/2011/03/24/1993856.html


    1:内存对齐定义: 
        现在使用的计算机中内存空间都是按照字节划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际上计算机系统对于基本数据类型在内存中的存放位置都有限制,要求这些数据存储首地址是某个数K的倍数,这样各种基本数据类型在内存冲就是按照一定的规则排列的,而不是一个紧挨着一个排放,这就是内存对齐。

    对齐模数: 
        内存对齐中指定的对齐数值K成为对齐模数(Alignment Modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。

    2:内存对齐的好处: 
        内存对齐作为一种强制的要求,第一简化了处理器与内存之间传输系统的设计,第二可以提升读取数据的速度。各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。 
        Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。但是如果想提升性能,应该注意内存对齐方式。 
    ANSI C标准中并没有规定相邻声明的变量在内存中一定要相邻。为了程序的高效性,内存对齐问题由编译器自行灵活处理,这样导致相邻的变量之间可能会有一些填充字节。对于基本数据类型(int char等),他们占用的内存空间在一个确定硬件系统下有确定的值。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。

    3:内存对齐策略: 
    微软C编译器(cl.exe for 80×86)的对齐策略: 
    第一: 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 
    备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。 
    第二: 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 
    备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 
    第三: 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 
    备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

    填充字节就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。对于结构体本身也存在着对齐要求,ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,但是可以更严格(但此非强制要求,VC7.1就仅仅是让它们一样严格)。C标准保证,任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说,数组各元素之间不会有空隙。

    总结规则如下: 
    0: 结构体变量的首地址能够被其最宽基本类型成员的大小所整除 
    1: VC6和VC71默认的内存对齐方式是 #pragam pack(8) 
    2: 结构体中每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐. 
    3:   结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍. 
    4:   结构体本身也存在着对齐要求规则,不能比它所有字段中要求最严格的那个宽松. 
    5: 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。 
    6: 在GCC中,对齐模数的准则是:对齐模数最大只能是 4,也就是说,即使结构体中有double类型,对齐模数还是4,所以对齐模数只能是1,2,4。 
          而且在上述的规则中,第3条里,offset必须是成员大小的整数倍: 
           (1): 如果这个成员大小小于等于4则按照上述准则是可行的, 
           (2): 如果成员的大小大于4,则结构体每个成员相对于结构体首地址的偏移量只能按照是4的整数倍来进行判断是否添加填充,且整个结构体的大小也是按4的整数倍来进行判断是否进行填充。

    例子1(VC8): 
    typedef struct ms1 { 
      char a; 
      int b; 
    } MS1;

    typedef struct ms2 { 
      int a; 
      char b; 
    } MS2; 
    MS1中有最强对齐要求的是b字段(int类型),字段a相对于首地址偏移量为0(1的倍数),直接存放,此时如果直接存放字段b,则字段b相对于结构体变量首地址的偏移量为1(不是4的倍数),填充3字节,b由偏移地址为4开始存放。也就是遵循了第2条与第3条规则,而对于结构体变量本身,根据规则4,对齐参数至少应该为4。根据规则5,sizeof(MS1) = 8; 同样MS2分析得到的结果也是如此。

    例子2VC8: 
    typedef struct ms3 { 
      char a; 
      short b; 
      double c; 
    } MS3;

    typedef struct ms4 { 
      char a; 
      MS3 b; 
    } MS4; 
    MS3中内存要求最严格的字段是c(8字节),MS3的对齐参数也是8字节; 那么MS4类型数据的对齐模数就与MS3中的double一致(为8),a字段后面应填充7个字节.sizeof(MS3) = 16; sizeof(MS4) = 24; 
    注意规则5中是说,结构体的总大小为结构体最宽基本类型成员大小的整数倍。注意是基本类型,这里的MS3不是基本类型。 
    对齐模数的选择只能是根据基本数据类型,所以对于结构体中嵌套结构体,只能考虑其拆分的基本数据类型。

    例子3(GCC): 
    struct T { 
      char ch; 
      double d ; 
    }; 
    在GCC下,sizeof(T)应该等于12个字节。VC8下为16字节。 
    ch为1字节,没有问题的,之后d的大小大于4,对于d的对齐模数只能是4,相对于结构体变量的首地址偏移量也只能是4,而不能使8的整数倍,由偏移量4开始存放,结构体共占12字节。 
    这里并没有执行第5条规则。

    位域情况: 

    位域:有些信息在存储时,并不需要占用一个完整的字节,而只需要占几个或一个二进制位。例如在存放一个开关变量时,只有0、1两种状态,用一位二进制位即可。为了节省存储空间,并使处理简单,C语言提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进制位划分为几个不同的区域,每个区域有一个域名,并指定每个区域的位数,允许在程序中按域名进行操作。

    C99规定int、unsigned   int和bool可以作为位域类型(vc编译器符合该规定,没有进行扩展)。

    如果结构体中含有位域(bit-field),总结规则如下 
    1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止; 
    2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍; 
    3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式; 
    4)如果位域字段之间穿插着非位域字段,则不进行压缩; 
    5) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。 
    备注:当两字段类型不一样的时候,对于不压缩方式,例如:

    struct N { 
      char c:2; 
      int i:4; 
    }; 
    依然要满足不含位域结构体内存对齐准则第3条,i成员相对于结构体首地址的偏移应该是4的整数倍,所以c成员后要填充3个字节,然后再开辟4个字节的空间作为int型,其中4位用来存放i,所以上面结构体在VC中所占空间为8个字节; 
    而对于采用压缩方式的编译器来说,遵循不含位域结构体内存对齐准则第3条,不同的是,如果填充的3个字节能容纳后面成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间,所以上面结构体N在GCC或者Dev- C++中所占空间应该是4个字节。

    例子4: 
    typedef struct { 
      char c:2; 
      double i; 
      int c2:4; 
    }N3; 
    按照含位域规则4,在GCC下占据的空间为16字节,在VC下占据的空间是24个字节。

    结论: 
    -------- 
    定义结构体的时候,成员最好能从大到小来定义,那样能相对的省空间。例如如下定义: 
    struct A { 
      double d; 
      int i; 
      char c; 
    }; 
    那么,无论是windows下的vc系列编译器,还是linux下的gcc,都是16字节。

    例子5: 
    typedef union student{ 
      char name[10]; 
      long sno; 
      char sex; 
      float score [4]; 
    } STU; 
    STU aa[5]; 
    cout<<sizeof(aa)<<endl; 
    union是可变的以其成员中最大的成员作为该union的大小16*5=80

    例子6: 
    typedef struct student{ 
      char name[10]; 
      long sno; 
      char sex; 
      float score [4]; 
    } STU; 
    STU aa[5]; 
    cout<<sizeof(aa)<<endl; 
    STU占空间为:10字节(char)+空2字节+4字节(long)+1字节(char)+空3字节+16字节(float)=36字节,36*5=180字节

    例子7(VC8.0): 
    typedef struct bitstruct { 
      int b1:5; 
      int b2:2; 
      int b3:3; 
    }bitstruct;

    int _tmain(int argc, _TCHAR* argv[]) { 
      bitstruct b; 
      memcpy(&b,"EM",sizeof(b)); 
      cout<<sizeof(b)<<endl; 
      cout<<b.b1<<endl<<b.b2<<endl<<b.b3; 
      return 0; 

    对于bitstruct是含有位域的结构体,sizeof(int)为4字节,按照规则1、2,首先b1占起始的5个字节, 根据含位域规则1, b2紧跟存放,b3也是紧跟存放的。 
    根据规则5,得到sizeof(bitstruct) = 4。 
    现在主流的CPU,intel系列的是采用的little endian的格式存放数据,motorola系列的CPU采用的是big endian. 
    以主流的little endian分析: 
    在进行内存分配的时候,首先分配bitstruct的第一个成员类型int(4字节),这四个字节的存放按照低字节存储在低地址中的原则。 
    int共4个字节: 
    第4个字节 - 第3个字节 - 第2个字节 - 第1个字节,

    在内存中的存放方式如下所示。 
    而后为b1分配5位,这里优先分配的应该是低5位,也就是第一个字节的低5位。 
    继而分配b2的2个字节,也就是第1个字节中紧接着的2位。 
    最后分配b3的3位,按照规则1、2,b3还是紧接着存放的,b3的最低位是第一个字节的最高位,高两位为第2个字节的低两位。 
    内存分配图如下所示:

    QQ截图未命名 
    字符E二进制为0100 0101,字符M的二进制为0100 1101,在内存中存放如下所示:

    QQ截图未命名2 
    memcpy为按位拷贝的,所以两片内存区可以直接对应上,得到 
    b1的二进制形式为:00101 ,高位为0,正数,5 
    b2的二进制形式为:10 ,高位为1,负数,取反加1,添加符号,-2 
    b3的二进制形式为:b3的最低一位是0,高位为01,拼接后为010,正数,2

    内存分配情况感觉蛮奇怪的,按如下修改例7,b1应该为5,b2为-2,b3为-6,VC8.0下验证正确。 
    typedef struct bitstruct { 
      int b1:5; 
      int b2:2; 
      int b3:4; 
    }bitstruct;

    int _tmain(int argc, _TCHAR* argv[]) { 
      bitstruct b; 
      memcpy(&b,"EM",sizeof(b)); 
      cout<<sizeof(b)<<endl; 
      cout<<b.b1<<endl<<b.b2<<endl<<b.b3; 
      return 0; 
    }

    4: 定义数组时的内存布局及内存字节对齐

    int b=10;

    int a[3]={1,2,3};

    int c=11;

    image

    int b=0x01020304;

    char ch='a';

    对于一个数0x01020304; 对于一个数0x1122

    使用Little Endian方式时,低地址存放低字节,由低地址向高地址存放为:4->3->2->1 
    而使用Big Endian方式时, 低地址存放高字节,由低地址向高地址存放为:1->2->3->4

    而在Little Endian模式中,b的地址所指的就是 : 低地址(存放的是最低的字节)

    image

       1:  void __cdecl func_cdcel(int i, char *szTest) {
       2:      cout << "szTest在栈中的地址:" << &szTest << endl;
       3:      cout << "szTest本身的值(指向的地址):" << (void*)szTest << endl<<endl;
       4:      
       5:      cout << "i在堆栈中地址:" << &i << endl;
       6:      cout << "i的地址:" << &i << endl;
       7:   
       8:      int k,k2;
       9:      cout << "局部变量k的地址:" << &k << endl;
      10:      cout << "局部变量k2的地址:" << &k2 << endl;
      11:      cout << "-------------------------------------------------------" << endl;
      12:  }
      13:   
      14:  void __stdcall func_stdcall(int i, char *szTest){
      15:      cout << "szTest在栈中的地址:" << &szTest << endl;
      16:      cout << "szTest本身的值(指向的地址):" << (void*)szTest << endl<<endl;
      17:   
      18:      cout << "i在堆栈中地址:" << &i << endl;
      19:      cout << "i的地址:" << &i << endl;
      20:   
      21:      int k,k2;
      22:      cout << "局部变量k的地址:" << &k << endl;
      23:      cout << "局部变量k2的地址:" << &k2 << endl;
      24:      cout << "-------------------------------------------------------" << endl;
      25:  }
      26:   
      27:  int main(){
      28:      int a[4];
      29:      cout <<"a[0]地址:"<< &a[0] << endl;
      30:      cout <<"a[1]地址:"<< &a[1] << endl;
      31:      cout <<"a[2]地址:"<< &a[2] << endl;
      32:      cout <<"a[3]地址:"<< &a[3] << endl;
      33:   
      34:      int i = 0x22;
      35:      int j = 8;
      36:      char szTest[4] = {'a','b', 'c', 'd'};
      37:      cout <<"i的地址:"<<&i << endl;
      38:      cout <<"szTest的地址:"<<(void*)szTest << endl;
      39:      func_cdcel(i, szTest);
      40:      func_stdcall(i, szTest);
      41:  }

    输出为:

    a[0]地址:0012FF54 
    a[1]地址:0012FF58 
    a[2]地址:0012FF5C 
    a[3]地址:0012FF60                  <— 可见存储方式如上图所示,a[3]在高地址,先入栈,而数组地址a为a[0]的地址(低地址) 
    i的地址:0012FF48                    <— 这里进行了内存对齐,i的起始地址必定是i所占内存大小的倍数 
    szTest的地址:0012FF30  

    szTest在栈中的地址:0012FE5C   
    szTest本身的值(指向的地址):0012FF30

    i在堆栈中地址:0012FE58           <— i在堆栈中的地址低于szTest,也就是说szTest是先入栈的 
    i的地址:0012FE58 
    局部变量k的地址:0012FE48 
    局部变量k2的地址:0012FE3C 
    ------------------------------------------------------- 
    szTest在栈中的地址:0012FE5C 
    szTest本身的值(指向的地址):0012FF30

    i在堆栈中地址:0012FE58 
    i的地址:0012FE58 
    局部变量k的地址:0012FE48 
    局部变量k2的地址:0012FE3C 


    展开全文
  • 内存对齐,值得一读的内容, 内存对齐,值得一读的内容, 内存对齐,值得一读的内容, 内存对齐,值得一读的内容,
  • 内存对齐与内存分配原则

    万次阅读 2016-05-18 14:30:19
    首先讲一个概念—-内存对齐一种提高内存访问速度的策略,cpu在访问未对其的内存需要经过两次内存访问,而经过内存对齐一次就可以了。(?)打个比方就是:操作系统在访问内存时,每次读取一定的长度(这个长度是系统...

    首先讲一个概念—-内存对齐

    一种提高内存访问速度的策略,cpu在访问未对其的内存需要经过两次内存访问,而经过内存对齐一次就可以了。(?)

    打个比方就是:操作系统在访问内存时,每次读取一定的长度(这个长度是系统默认的对其系数),程序中你也可以自己设定对齐系数,告诉编译器你想怎么对齐,可用#pargam pack(n),指定n为对其系数。但是当没有了内存对齐,cpu在访问一个变量时候,可能会访问两次,为什么呢?
    32位cpu一次能最多处理的信息是32bit位,如果你没有指定对齐,我们假设这样的数据结构在内存中存在的情况,这也是我们后面要讨论的结构

    typedef strutc test{
        char a;
        int b;
        char c;
    } 

    对应的在内存中存放的方式可能是这样(假定32位下):
    这里写图片描述

    那么,这样一来,取得这个int型变量需要经过两次的寻址拼凑成一个完整的4字节的数。这个过程还涉及到cpu指令集调用和总线的读写操作,如果真是没有对齐的话,效率会差到不知道哪儿去了。
    所以这个内存对齐是必须遵守的,为了提高cpu访问效率和速度。

    继续引入另外一个概念:内存的自然对齐:每一种数据类型都必须放在地址中的整数倍上
    举个例子如下:
    地址4可以放char(1)类型,可以放int(4)型,可以放short(2)型,但是不能存放double(8)型,仅仅因为4不是8的整数倍。
    地址3能存放char型,但是其他int,short,double都不能存放。
    有一个特殊地址,就是0,它可以是任何类型的整数倍,所以可以存放任何数据。
    根据这个规则,那么在分配一大块包含很多变量的内存的时候,会产生很多碎片,具体到下面分析

    接下来,我们对这个结构体来进行一个分析:

    #include<stdio.h>
    #include<stdlib.h>
    typedef strutc test{
        char a;
        int b;
        double c;
        char d;
    } 
    
    int main(void)
    {
        STU s;
        printf("s的大小是 = %d\n",(int)sizeof(STU));    
        printf("s中a的起始地址是 %p\n",&(s.a));    
        printf("s中b的起始地址是 %p\n",&(s.b));
        printf("s中c的起始地址是 %p\n",&(s.c));
        printf("s中d的起始地址是 %p\n",&(s.d));
    
        return 0;
    }
    
    
    /*64位下
    s的大小是  = 24
    s中a的起始地址是 0x7ffd01319d10
    s中b的起始地址是 0x7ffd01319d14
    s中c的起始地址是 0x7ffd01319d18
    s中d的起始地址是 0x7ffd01319d20
    */

    依照简单的4字节对齐(gcc默认是4字节对齐),首先的char在0上(其实也就是某个4的整数倍数上),之后int b在地址4上,一直延续到8,double c就在地址8上,之后sizeof必须是8的整数倍,之前是4+4+8 == 16,那这个char就只能存入一个8大小的内存中了,也就是4+4+8+8 == 24

    为什么这么算呢?
    开始的可以根据内存的自然对齐求得,最后的char补7个空白是因为结构体的总大小,必须要是其内部最大成员的整数倍,不足的要补齐,像这里就是double 8个字节的整数倍,所以给最后的d补上了7个空白空间。这也是内存分配的3个原则之一。

    关于内存分配的其他两个规则如下:
    1.结构体或union联合的数据成员,第一个数据成员是要放在offset == 0的地方,如果遇上子成员,要根据子成员的类型存放在对应的整数倍的地址上
    2.如果结构体作为成员,则要找到这个结构体中的最大元素,然后从这个最大成员的整数倍地址开始存储(strutc a中有一个struct b,b里面有char,int,double….那b应该从8的整数倍开始存储)

    还需要注意一点:

    typedef struct stu{
        char a;
        int b;
        char ex;
        double c;
        char d;
    }STU;
    
    printf("STU的大小是 = %d\n",(int)sizeof(STU));  
    
    /*32位输出
    STU的大小是 = 24
    */
    
    /*64位输出
    STU的大小是 = 32
    */

    计算一下出来的结果和输出是否正确:
    32位:a( 4 )+b( 4 )+ex( 4 )+c( 4+4 )+d( 4 ) == 24
    64位:a( 4 )+b( 4 )+ex( 8 )+c( 8 )+d( 8 ) == 32

    而为什么会出现这样的结果呢?准确一点为什么32位中的double变成了2个4字节而不是一个8?

    这需要结合之前说的内存的自然对齐,我们知道char遇到int型,产生3个空白,遇上double要产生7个空白。而这里32位中的char遇上double却只是产生了3个空白,是因为32位限制了一次只能读入4个字节数据处理,也就是说8字节的double被分成了2个4字节字符被处理,也可以说死了,32位平台下就定死了4字节对齐(当然你可以设定更小,但是没什么意义),接着说结构体,那结构体中最大的数就是4字节的了,sizeof(STU)也只需要遵守是4的整数倍即可。最后得到24字节。

    64位就按正常的计算。

    最后贴上这一段代码,是下面参考博文中某个作者总结的一段,只要能看懂这段代码就大抵上全部理解3个内存对其和分配原则了

    typedef struct bb
    {
     int id;             //[0]....[3]
     double weight;      //[8].....[15]      原则1
     float height;      //[16]..[19],总长要为8的整数倍,补齐[20]...[23]     原则3
    }BB;
    
    typedef struct aa
    {
     char name[2];     //[0],[1]
     int  id;         //[4]...[7]          原则1
    
     double score;     //[8]....[15]    
     short grade;    //[16],[17]        
     BB b;             //[24]......[47]          原则2
    }AA;
    
    int main()
    {
      AA a;
      cout<<sizeof(a)<<" "<<sizeof(BB)<<endl;
      return 0;
    }
    
    /*输出
    48 24
    */

    总结
    内存对齐的目的和好处
    内存自然对其的概念
    内存对齐(分配)的3个原则

    有什么错误还请指出

    参考博文链接:
    http://blog.csdn.net/hairetz/article/details/4084088
    http://blog.csdn.net/msdnwolaile/article/details/50158463
    http://www.cnblogs.com/xudong-bupt/archive/2013/05/13/3076024.html
    http://blog.csdn.net/Ropyn/article/details/6568780

    展开全文
  • c/c++深入篇之内存分配内存对齐的探讨

    千次阅读 多人点赞 2014-12-15 17:55:13
    c/c++深入篇之内存分配内存对齐的探讨 在大多数低层程序设计中,由于内存分配内存对齐问题所带来的bug所占比重非常大。本文对内存分配中的分配空间类型、作用、方法、适用范围、优缺点以及内存对齐问题中的对齐...
    

    不明白内存分配和指针的可以看看,其实这本是我们老师留的一个操作系统科技小论文作业,不知道写什么,干脆把以前收藏的经典C内存分配的文章整理并修改了一下。       此文章有2个用处,1:这是个小论文,格式完整,大家可以复制回去交作业;2:这是整理的经典C内存分配小教程(也加了些我自己的观点),不明白内存分配的可以看看。

    还有很重要的一个问题:      这篇文章引用的很多内容我也不知道究竟是出自谁手,知道作者是谁的麻烦告诉下,我好谢谢他。(记得都是csdn里面找的)

    tag: 操作系统 论文 内存分配 内存对齐 c语言内存分配 免费论文下载.doc

    正文:

    关于程序设计的内存分配问题

    freec

    (辽宁工程技术大学 软件学院 SJ07-3 辽宁 葫芦岛 125000

    [  ]

    在大多数低层程序设计中,由于内存分配与内存对齐问题所带来的bug所占比重非常大。本文对内存分配中的分配空间类型、作用、方法、适用范围、优缺点以及内存对齐问题中的对齐原因、对齐规则等进行了详细的说明,并结合大量c语言代码进行阐述与分析。

    [关键词]

    内存分配;堆栈原理;内存对齐;

     

    1 引言

    操作系统的内存分配问题与内存对齐问题对于低层程序设计来说是非常重要的,对内存分配的理解直接影响到代码质量、正确率、效率以及程序员对内存使用情况、溢出、泄露等的判断力。而内存对齐是常常被忽略的问题,理解内存对齐原理及方法则有助于帮助程序员判断访问非法内存。

     

    2 程序的内存分配问题

     

    一、一般C/C++程序占用的内存主要分为5

        1、栈区(stack):类似于堆栈,由程序自动创建、自动释放。函数参数、局部变量以及返回点等信息都存于其中。

        2、堆区(heap): 使用自由,不需预先确定大小。多数情况下需要由程序员手动申请、释放。如不释放,程序结束后由操作系统垃圾回收机制收回。

        3、全局区/静态区(static):全局变量和静态变量的存储是区域。程序结束后由系统释放。

        4、文字常量区:常量字符串就是放在这里的。 程序结束后由系统释放。

    5、程序代码区:既可执行代码。

     

    例:

    #include <stdio.h>

    int quanju;/*全局变量,全局区/静态区(static*/

    void fun(int f_jubu); /*程序代码区*/

    int main(void)/**/

    {

           int m_jubu;/*栈区(stack*/

           static int m_jingtai;/*静态变量,全局区/静态区(static*/

           char *m_zifum,*m_zifuc = "hello";/*指针本身位于栈。指向字符串"hello",位于文字常量区*/

           void (*pfun)(int); /*栈区(stack*/

           pfun=&fun;

           m_zifum = (char *)malloc(sizeof(char)*10);/*指针内容指向分配空间,位于堆区(heap*/

           pfun(1);

           printf("&quanju   : %x/n",&quanju);

           printf("&m_jubu   : %x/n",&m_jubu);

           printf("&m_jingtai: %x/n",&m_jingtai);

           printf("m_zifuc   : %x/n",m_zifuc);

           printf("&m_zifuc  : %x/n",&m_zifuc);

           printf("m_zifum   : %x/n",m_zifum);

           printf("&m_zifum  : %x/n",&m_zifum);

           printf("pfun      : %x/n",pfun);

           printf("&pfun     : %x/n",&pfun);

           getch();

           return 0;

    }

    void fun(int f_jubu)

    {

           static int f_jingtai;

           printf("&f_jingtai: %x/n",&f_jingtai);

           printf("&f_jubu   : %x/n",&f_jubu);/*栈区(stack,但是与主函数中m_jubu位于不同的栈*/

    }

     

    输出结果:

    &f_jingtai: 404020

    &f_jubu   : 22ff40

    &quanju   : 404070

    &m_jubu   : 22ff74

    &m_jingtai: 404010

    m_zifuc   : 403000

    &m_zifuc  : 22ff6c

    m_zifum   : 3d24e0

    &m_zifum  : 22ff70

    pfun      : 4013af

    &pfun     : 22ff68

     

    分析:

    堆区:

           m_zifum   : 3d24e0

    代码区:

           pfun      : 4013af

    局区/静态区(static:

           m_zifuc   : 403000 

           &m_jingtai: 404010

           &f_jingtai: 404020

           &quanju   : 404070

    栈区:

           &f_jubu   : 22ff40 fun函数栈区

           &pfun     : 22ff68 主函数栈区 

           &m_zifuc  : 22ff6c

           &m_zifum  : 22ff70

           &m_jubu   : 22ff74

     

    二、堆和栈

    1申请方式

    stack:

    由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

    heap:

    需要程序员手动申请,并指明大小,在c中,有malloc函数完成

    p1 = (char *)malloc(10);

    C++中用new运算符

    p2 = (char *)malloc(10);

    但是注意p1p2本身是在栈中的。 

     

    2 申请后系统的响应

    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

    堆:大多数操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free函数才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

     

    3申请大小的限制

    栈:在Windows,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

     

    4申请效率的比较:

    栈由系统自动分配,速度较快。但程序员是无法控制的。

    堆是由程序员手动分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

    另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

     

    5堆和栈中的存储内容

    栈: 在函数调用时,第一个进栈的是函数调用语句的下一条可执行语句的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是函数中的下一条指令,程序由该点继续运行。

    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

     

    6存取效率的比较

    char s1[] = "aaaaaaaaaaaaaaa";

    char *s2 = "bbbbbbbbbbbbbbbbb";

    aaaaaaaaaaa是在运行时刻赋值的;

    bbbbbbbbbbb是在编译时就确定的;

    但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。

    比如:

    #include

    void main()

    {

    char a = 1;

    char c[] = "1234567890";

    char *p ="1234567890";

    a = c[1];

    a = p[1];

    return;

    }

    对应的汇编代码

    : a = c[1];

    00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]

    0040106A 88 4D FC mov byte ptr [ebp-4],cl

    : a = p[1];

    0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]

    00401070 8A 42 01 mov al,byte ptr [edx+1]

    00401073 88 45 FC mov byte ptr [ebp-4],al

    第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了一些。

     

    2 内存对齐问题

     

    一、内存对齐的原因

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

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

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

     

    二、对齐规则

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

    规则:

    1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员

    自身长度中,比较小的那个进行。

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

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

     

    三、试验

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

    编译器:GCC 3.4.2VC6.0

    平台:Windows XP

     

    典型的struct对齐

    struct定义:

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

    struct test_t {

     int a;

     char b;

     short c;

     char d;

    };

    #pragma pack(n)

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

    sizeof(char) = 1

    sizeof(short) = 2

    sizeof(int) = 4

     

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

     

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

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

    分析过程:

    1) 成员数据对齐

    #pragma pack(1)

    struct test_t {

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

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

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

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

    };

    #pragma pack()

    成员总大小=8

     

    2) 整体对齐

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

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

     

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

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

    分析过程:

    1) 成员数据对齐

    #pragma pack(2)

    struct test_t {

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

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

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

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

    };

    #pragma pack()

    成员总大小=9

    2) 整体对齐

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

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

     

    34字节对齐(#pragma pack(4))

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

    分析过程:

    1) 成员数据对齐

    #pragma pack(4)

    struct test_t {

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

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

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

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

    };

    #pragma pack()

    成员总大小=9

     

    2) 整体对齐

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

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

     

    48字节对齐(#pragma pack(8))

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

    分析过程:

    1) 成员数据对齐

    #pragma pack(8)

    struct test_t {

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

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

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

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

    };

    #pragma pack()

    成员总大小=9

    2) 整体对齐

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

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

     

    516字节对齐(#pragma pack(16))

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

    分析过程:

    1) 成员数据对齐

    #pragma pack(16)

    struct test_t {

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

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

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

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

    };

    #pragma pack()

    成员总大小=9

     

    2) 整体对齐

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

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

    8字节和16字节对齐试验证明了“规则”的第3点:“当#pragma packn值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果”。

     

    4结束语

    内存分配与内存对齐是个很复杂的东西,不但与具体实现密切相关,而且在不同的操作系统,编译器或硬件平台上规则也不尽相同,虽然目前大多数系统/语言都具有自动管理、分配并隐藏低层操作的功能,使得应用程序编写大为简单,程序员不在需要考虑详细的内存分配问题。但是,在系统或驱动级以至于高实时,高保密性的程序开发过程中,程序内存分配问题仍旧是保证整个程序稳定,安全,高效的基础。

     

     

    [参考文献及技术支持]

    [1] Brian.W.Kerighan <the C programming language> 2004.1

    [2] W.richard stevens <unix环境高级编程> 2006.10

    [3]csdn开发社区 c/c++版块 提供技术支持

    [4]50M深蓝程序设计讨论组 提供技术支持 

    [1]

    什么是“圆整”?

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

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

    原文链接:http://blog.csdn.net/cuibo1123/article/details/2547442

    展开全文
  • C++中的class、struct、union首地址内存对齐到第一个数据成员字节大小的整数倍,如第一个是short(2字节)则首地址是2字节的整数倍,class、struct内存大小是对齐到最大字节的整数倍。union的大小以大的成员为准。...
  • 内存分配对齐策略

    2011-11-01 16:38:24
    内存分配对齐策略 学习c/c++内存管理分配机制的同学们,这是一本必看的文档,你懂的
  • 内存对齐以及如何关闭内存对齐

    千次阅读 2015-10-08 09:28:52
    内存对齐以前接触过,知道有这么回事,昨天面试,面试官问了一个结构体内存分配相关的问题: struct _A{ int a; int b; char c; }A; sizeof(A)=?(32位机器) 我一想这要涉及到内存对齐啊,sizeof(A)=12 啊, 没想到...
  • 在大多数低层程序设计中,由于内存分配内存对齐问题所带来的bug所占比重非常大。本文对内存分配中的分配空间类型、作用、方法、适用范围、优缺点以及内存对齐问题中的对齐原因、对齐规则等进行了详细的说明,并...
  • 内存对齐分配原则

    2018-05-15 10:03:54
    这不是理想中的没有内存对齐的世界吗.没错,#pragma pack(1),告诉编译器,所有的对齐都按照1的整数倍对齐,换句话说就是没有对齐规则. 可供参考: http://blog.csdn.net/msdnwolaile/article/details/50158463 ...
  • 关于程序设计的内存分配...本文对内存分配中的分配空间类型、作用、方法、适用范围、优缺点以及内存对齐问题中的对齐原因、对齐规则等进行了详细的说明,并结合大量c语言代码进行阐述与分析。 [关键词] 内存分配
  • 转自:http://blog.csdn.net/cuibo1123/archive/2008/06/14/2547442.aspx 1 引言操作系统的内存分配问题与内存对齐问题对于低层程序设计来说是非常重要的,对内存分配的理解直接影响到代码质量、正确率、效率以及...
  • 内存分配对齐

    千次阅读 2014-10-28 16:21:39
    1. 调用palloc()函数,实现动态分配地址对齐内存(但是很多系统中默认是不提供这个接口的,不过大多数底层硬件商都在里面做了这个接口) 2. 动态分配对齐内存:自己在malloc()函数的基础上进行封装(推荐使用...
  • 操作系统的内存分配问题与内存对齐问题对于低层程序设计来说是非常重要的,对内存分配的理解直接影响到代码质量、正确率、效率以及程序员对内存使用情况、溢出、泄露等的判断力。而内存对齐是常常被忽略的问题,理解...
  • 数据结构还有变量等等都需要占有内存,在很多系统中,它都要求内存分配的时候要对齐,这样做的好处就是可以提高访问内存的速度。 #include iostream>  2 using namespace std;  3   4 struct X1  5...
  • 硬件原因:有的平台或者特定的硬件对内存访问有对齐要求 性能原因:cpu读取不对齐内存可能会造成两次读取操作,影响性能 二)代码实现 例:比如要求 8bit 对齐内存只能是8的倍数 aligned_malloc( 17, 8); ...
  • 本文以程序实例阐述了,如何在程序当中实现任意字节对齐
  • 内存对齐

    2020-09-23 22:51:39
    文章目录结构体内存对齐什么是结构体内存对齐内存对齐实例n字节对齐方式人为设定n字节对齐方式设定n字节对齐方式实例内存对齐的作用总结 结构体内存对齐 什么是结构体内存对齐? 默认的对齐方式:各成员变量在存放...
  • 数据结构还有变量等等都需要占有内存,在很多系统中,它都要求内存分配的时候要对齐,这样做的好处就是可以提高访问内存的速度。 #include   2 using namespace std;  3   4 struct X1  5 {  6 ...
  • 意思是以a为对齐因子,申请d大小的空间,实际至少需要分配多大空间来实现内存对齐。 例如,按16字节对齐,申请56个字节的空间,为了内存对齐实际分配的是64字节。 1)先看~(a-1)的含义 假设a是8 对应二...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 82,842
精华内容 33,136
关键字:

内存对齐分配