zoukankan      html  css  js  c++  java
  • C/C++中的内存补齐机制

         在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。例如,下面的结构各成员空间分配情况:
    struct test
    {
         char x1;
         short x2;
         float x3;
         char x4;
    };
    结构的第一个成员x1为char类型,其自然对界为1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其自然对界为2,起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然对界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大对界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

          更改C编译器的缺省字节对齐方式
    在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
         · 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
         · 使用伪指令#pragma pack (),取消自定义字节对齐方式。
    另外,还有如下的一种方式:
         · __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
         · __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
    以上的n = 1, 2, 4, 8, 16... 第一种方式较为常见。
    http://blog.csdn.net/wenddy112/articles/300583.aspx

    应用实例
      在网络协议编程中,经常会处理不同协议的数据报文。一种方法是通过指针偏移的方法来得到各种信息,但这样做不仅编程复杂,而且一旦协议有变化,程序修改起来也比较麻烦。在了解了编译器对结构空间的分配原则之后,我们完全可以利用这一特性定义自己的协议结构,通过访问结构的成员来获取各种信息。这样做,不仅简化了编程,而且即使协议发生变化,我们也只需修改协议结构的定义即可,其它程序无需修改,省时省力。下面以TCP协议首部为例,说明如何定义协议结构。其协议结构定义如下:

    #pragma pack(1) // 按照1字节方式进行对齐
    struct TCPHEADER
    {
         short SrcPort; // 16位源端口号
         short DstPort; // 16位目的端口号
         int SerialNo; // 32位序列号
         int AckNo; // 32位确认号
         unsigned char HaderLen : 4; // 4位首部长度
         unsigned char Reserved1 : 4; // 保留6位中的4位
         unsigned char Reserved2 : 2; // 保留6位中的2位
         unsigned char URG : 1;
         unsigned char ACK : 1;
         unsigned char PSH : 1;
         unsigned char RST : 1;
         unsigned char SYN : 1;
         unsigned char FIN : 1;
         short WindowSize; // 16位窗口大小
         short TcpChkSum; // 16位TCP检验和
         short UrgentPointer; // 16位紧急指针
    };
    #pragma pack() // 取消1字节对齐方式

    下面有一道在 CSDN论坛 上讨论火热的题:

    #pragma pack(8)
    struct s1{
    short a;
    long b;
    };
    struct s2{
    char c;
    s1 d;
    long long e;
    };
    #pragma pack()

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


    结果如下:
    sizeof(S2)结果为24.
    成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.
    也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.
    S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;
    S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.
                              a    b
    S1的内存布局:11**,1111,
                              c    S1.a S1.b     d
    S2的内存布局:1***,11**,1111,****11111111
    这里有三点很重要:
    1.每个成员分别按自己的方式对齐,并能最小化长度
    2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
    3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐


          补充一下,对于数组,对齐方式为元素的对齐方式。比如:
    char a[3];这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
    如果写: typedef char Array3[3];
    Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
    不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.
    struct s1
    {
    char a[8];
    };
    struct s2
    {
    double d;
    };
    struct s3
    {
    s1 s;
    char a;
    };
    struct s4
    {
    s2 s;
    char a;
    };
    cout<<sizeof(s1)<<endl;//8
    cout<<sizeof(s2)<<endl;//8
    cout<<sizeof(s3)<<endl;//9
    cout<<sizeof(s4)<<endl;//16;
    s1和s2大小虽然都是8,但是s1的对齐方式是1,s2是8(double),所以在s3和s4中才有这样的差异。
    所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。

          Union的对齐方式:为成员中最大的对齐方式,长度为按照这个对齐方式调整最长成员得到的长度。
    union u
    {
    double a;
    int b;
    };
    union u2
    {
    char a[13];
    int b;
    };
    union u3
    {
    char a[13];
    char b;
    };
    cout<<sizeof(u)<<endl;//8
    cout<<sizeof(u2)<<endl;//16
    cout<<sizeof(u3)<<endl;//13
    都知道union的大小取决于它所有的成员中,占用空间最大的一个成员的大小。所以对于u来说,大小就是最大的double类型成员a了,所以 sizeof(u)=sizeof(double)=8。但是对于u2和u3,最大的空间都是char[13]类型的数组,为什么u3的大小是13,而 u2是16呢?关键在于u2中的成员int b。由于int类型成员的存在,使u2的对齐方式变成4,也就是说,u2的大小必须在4的对界上,所以占用的空间变成了16(最接近13的对界)。


          结论:复合数据类型,如union,struct,class的对齐方式为成员中对齐方式最大的成员的对齐方式。


    顺便提一下CPU对界问题,32的C++采用8位对界来提高运行速度,所以编译器会尽量把数据放在它的对界上以提高内存命中率。对界是可以更改的,使用#pragmapack(x)宏可以改变编译器的对界方式,默认是8。C++固有类型的对界取编译器对界方式与自身大小中较小的一个。例如,指定编译器按2对界,int类型的大小是4,则int的对界为2和4中较小的2。在默认的对界方式下,因为几乎所有的数据类型都不大于默认的对界方式8(除了 longdouble),所以所有的固有类型的对界方式可以认为就是类型自身的大小。 www. 更改一下上面的程序:
    #pragmapack(2)
    union u2
    {
    char a[13];
    int b;
    };
    union u3
    {
    char a[13];
    char b;
    };
    #pragmapack(8)
    cout<<sizeof(u2)<<endl;//14
    cout<<sizeof(u3)<<endl;//13
    由于手动更改对界方式为2,所以int的对界也变成了2,u2的对界取成员中最大的对界,也是2了,所以此时sizeof(u2)=14。
    结论:C++固有类型的对界取编译器对界方式与自身大小中较小的一个。

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/digu/archive/2009/11/04/4768623.aspx

  • 相关阅读:
    HDU 4611 Balls Rearrangement 数学
    Educational Codeforces Round 11 D. Number of Parallelograms 暴力
    Knockout.Js官网学习(简介)
    Entity Framework 关系约束配置
    Entity Framework Fluent API
    Entity Framework DataAnnotations
    Entity Framework 系统约定配置
    Entity Framework 自动生成CodeFirst代码
    Entity Framework CodeFirst数据迁移
    Entity Framework CodeFirst尝试
  • 原文地址:https://www.cnblogs.com/lancidie/p/1952649.html
Copyright © 2011-2022 走看看