zoukankan      html  css  js  c++  java
  • #pragma pack

    为了能使CPU对变量进行高效快速的访问,变量的起始地址应该具有某些特性,即所谓的“对齐”。例如对于4字节的int类型变量,其起始地址应位于4字节边界上,即起始地址能够被4整除。变量的对齐规则如下(32位系统):
    Type
    Alignment
    char
    在字节边界上对齐
    short (16-bit)
    在双字节边界上对齐
    int and long (32-bit)
    在4字节边界上对齐
    float
    在4字节边界上对齐
    double
    在8字节边界上对齐
    structures
    单独考虑结构体的个成员,它们在不同的字节边界上对齐。
    其中最大的字节边界数就是该结构的字节边界数。

    如果结构体中有结构体成员,那么这是一个递归的过程。
    设编译器设定的最大对齐字节边界数为n,对于结构体中的某一成员item,它相对于结构首地址的实际字节对齐数

    目X应该满足以下规则:
    X = min(n, sizeof(item))
    例如,对于结构体
    struct {
    char a;
    long b;
    } T;
    当位于32位系统,n=8时:
    a的偏移为0,
    b的偏移为4,中间填充了3个字节, b的X为4;

    当位于32位系统,n=2时:
    a的偏移为0,
    b的偏移为2,中间填充了1个字节,b的X为2;
    结构体的sizeof:
    设结构体的最后一个成员为LastItem,其相对于结构体首地址的偏移为offset(LastItem),其大小为sizeof(LastItem),结构体的字节对齐数为N,则:结构体的sizeof 为: 若offset(LastItem)+ sizeof(LastItem)能够被N整除,那么就是offset(LastItem)+ sizeof(LastItem),否则,在后面填充,直到能够被N整除。
    另外:
    1) 对于空结构体,sizeof == 1;因为必须保证结构体的每一个实例在内存中都有独一无二的地址.
    2)结构体的静态成员不对结构体的大小产生影响,因为静态变量的存储位置与结构体的实例地址无关。例如:
    struct {static int I;} T; struct {char a; static int I;} T1;
    sizeof(T) == 1; sizeof(T1) == 1;

    下面是CSDN上提出的问题(原文<>:http://community.csdn.net/Expert/TopicView3.asp?id=3804035)
    ---------------------------------------
    #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?
    ---------------------------------------

    下面是redleaves(ID最吊的网友)给出的解释:
    很详尽,很透彻,从论坛原文后头的对话可以看出redleaves对C/C++的标准理解很深刻,值得偶学习:)
    #pragma pack(8)
    struct S1{
    char a;
    long b;
    };
    struct S2 {
    char c;
    struct S1 d;
    long long e;
    };
    #pragma pack()
    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....中的一个.

    如下一段代码:
      #pragma pack(4)
      class TestB
      {
      public:
        int aa;
        char a;
        short b;
        char c;
      };
      int nSize = sizeof(TestB);
      这里nSize结果为12,在预料之中。

      现在去掉第一个成员变量为如下代码:
      #pragma pack(4)
      class TestC
      {
      public:
        char a;
        short b;
        char c;
      };
      int nSize = sizeof(TestC);
      按照正常的填充方式nSize的结果应该是8,为什么结果显示nSize为6呢?

    事实上,很多人对#pragma pack的理解是错误的。
    #pragma pack规定的对齐长度,实际使用的规则是:
    结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
    也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。
    而结构整体的对齐,则按照结构体中最大的数据成员和 #pragma pack指定值 之间,较小的那个进行。

    具体解释
    #pragma pack(4)
      class TestB
      {
      public:
        int aa; //第一个成员,放在[0,3]偏移的位置,
        char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
        short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
        char c; //第四个,自身长为1,放在[8]的位置。
      };
    这个类实际占据的内存空间是9字节
    类之间的对齐,是按照类内部最大的成员的长度,和#pragma pack规定的值之中较小的一个对齐的。
    所以这个例子中,类之间对齐的长度是min(sizeof(int),4),也就是4。
    9按照4字节圆整的结果是12,所以sizeof(TestB)是12。


    如果
    #pragma pack(2)
    class TestB
      {
      public:
        int aa; //第一个成员,放在[0,3]偏移的位置,
        char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
        short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
        char c; //第四个,自身长为1,放在[8]的位置。
      };
    //可以看出,上面的位置完全没有变化,只是类之间改为按2字节对齐,9按2圆整的结果是10。
    //所以 sizeof(TestB)是10。

    最后看原贴:
    现在去掉第一个成员变量为如下代码:
      #pragma pack(4)
      class TestC
      {
      public:
        char a;//第一个成员,放在[0]偏移的位置,
        short b;//第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
        char c;//第三个,自身长为1,放在[4]的位置。
      };
    //整个类的大小是5字节,按照min(sizeof(short),4)字节对齐,也就是2字节对齐,结果是6
    //所以sizeof(TestC)是6。

    感谢 Michael 提出疑问,在此补充:

    #pragma pack



    当数据定义中出现__declspec( align() )时,指定类型的对齐长度还要用自身长度和这里指定的数值比较,然后取其中较大的。最终类/结构的对齐长度也需要和这个数值比较,然后取其中较大的。

    可以这样理解, __declspec( align() ) 和 #pragma pack是一对兄弟,前者规定了对齐的最小值,后者规定了对齐的最大值,两者同时出现时,前者拥有更高的优先级。
    __declspec ( align() )的一个特点是,它仅仅规定了数据对齐的位置,而没有规定数据实际占用的内存长度,当指定的数据被放置在确定的位置之后,其后的数据填充仍然是按照#pragma pack规定的方式填充的,这时候类/结构的实际大小和内存格局的规则是这样的:
    在__declspec( align () )之前,数据按照#pragma pack规定的方式填充,如前所述。当遇到__declspec( align() )的时候,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为 max(数据自身长度,指定值) ),然后把被指定的数据类型从这个点开始填充,其后的数据类型从它的后面开始,仍然按照#pragma pack填充,直到遇到下一个__declspec( align() )。
    当所有数据填充完毕,把结构的整体对齐数值和__declspec( align() )规定的值做比较,取其中较大的作为整个结构的对齐长度。
    特别的,当__declspec( align() )指定的数值比对应类型长度小的时候,这个指定不起作用。

  • 相关阅读:
    在Ubuntu中通过update-alternatives切换软件版本
    SCons: 替代 make 和 makefile 及 javac 的极好用的c、c++、java 构建工具
    mongodb 的使用
    利用grub从ubuntu找回windows启动项
    How to Repair GRUB2 When Ubuntu Won’t Boot
    Redis vs Mongo vs mysql
    java script 的工具
    python 的弹框
    how to use greendao in android studio
    python yield的终极解释
  • 原文地址:https://www.cnblogs.com/justloveyou/p/2726183.html
Copyright © 2011-2022 走看看