• 数据对齐详解


    Author:bakari           Date:2012.8.26

    数据对齐实际上是内存字节的对齐,今天偶然翻开自己以前做的笔记,发现做了好多的题,但现在对于我来说觉得很陌生。上网查了一下数据对齐的原因和方式,现在把它整理出来以备之后的学习复习巩固。

    转载请注出处:http://www.cnblogs.com/bakari/archive/2012/08/27/2658956.html

    一、什么是数据对齐

    1、现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

    2、访问数据的地址要满足一定的条件,能被这个数据的长度所整除。 例如,1字节数据已经是对齐的,2字节的数据的地址要被2整除,4字节的数据地址要 被4整除。

    3、 数据对齐并不是操作系统的内存结构的一部分,而是C P U结构的一部分。

    4、 当C P U访问正确对齐的数据时,它的运行效率最高。当数据大小的数据模数的内存地址是0时,数据是对齐的。例如, W O R D值应该总是从被2除尽的地址开始,而D W O R D值应该总是从被4除尽的地址开始,如此等等。当C P U试图读取的数据值没有正确对齐时, C P U可以执行两种操作之一。即它可以产生一个异常条件,也可以执行多次对齐的内存访问,以便读取完整的未对齐数据值。

    二、对齐的原因

    1、 现在各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低 字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。

    2、数据对齐是为了读取数据的效率。假如说每一次 读取数据时都是一个字节一个字节读取,那就不需要对齐了,这跟读一个字节没有什 么区别,就是多读几次。但是这样读取数据效率不高。为了提高读取数据的带宽,现 代存储系统都采用许多并行的存储芯片来提高读取效率。

    三、数据对齐的实现

    通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择适合目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。

    1、究竟数据在内存中是如何实现对齐的,对齐的细节以及对齐的方式编译器是如何展示的?请参考这篇文章,文章中说的很清楚:http://blog.csdn.net/arethe/article/details/2548867,本篇文章主要是通过一些典型的例子进行说明和巩固。

    2、数据对齐的实现方式有两种,自然对齐(即默认对齐)和强制对齐

    1)、自然对齐

           一般编译器如VS2003-VS2010,CB,DEV C++等编译器的对齐位,默认都是8位,即#pragma pack(value) value = 8。

    看一个典型的例子

     1 #include <iostream>
     2 using namespace std;
     3 
     4 struct A
     5 {
     6     char _iC1;
     7     long _il;
     8     char _iC2;
     9     double _id;
    10 };
    11 //打乱顺序
    12 struct B
    13 {
    14     char _iC1;
    15     char _iC2;
    16     long _il;
    17     double _id;
    18 };
    19 int main(){
    20     cout << sizeof(A) << endl;
    21     cout << sizeof(B) << endl;
    22 
    23     return 0;
    24 }

    分析:

          对于A:_iC1占一个字节,long类型为4个字节,为了让之后的long类型自然对齐,需要增加3个字节,内存中用cc表示,即增加3cc(下同),_il 占4个字节,_iC2占1个字节,为了让double对齐,增加7cc,之后_id 占8个字节。所以sizeof(A) = 1+3+4+1+7+8 = 24个字节。

          对于B:同理,sizeof(B) = 1+1+2+4+8 = 16

    验证:

     总结:

          各成员变量存放的地址相对于结构的起始地址的偏移量为sizeof(类型)或其整数倍。结构的总大小是其成员中最大类型的sizeof(该类型)整数倍。所以在定义结构体时最好把结构中的变量按照类型大小从小到大声明,以减少中间的填补空间。

    2)、强制对齐,即人为修改#pragma pack(value) 中value的值

     1 #include <iostream>
     2 using namespace std;
     3 
     4 #pragma pack(4)     //Note!
     5 struct A
     6 {
     7     char _iC1;
     8     long _il;
     9     char _iC2;
    10     double _id;
    11 };
    12 #pragma pack()
    13 //打乱顺序
    14 struct B
    15 {
    16     char _iC1;
    17     char _iC2;
    18     long _il;
    19     double _id;
    20 };
    21 int main(){
    22     cout << sizeof(A) << endl;
    23     cout << sizeof(B) << endl;
    24 
    25     return 0;
    26 }

    注意:此处sizeof(A) = 20

    分析 :这个地方只在_iC2占有的字节数有所改动,_iC2的有效对齐值不再是double类型的字节数,而是强制对齐值和自身对齐值(即后一个类型的对齐值)的最小值,4 < 8,所以有效对齐值是4.所以:

    sizeof(A) = 1+3+4+1+3+8 = 20

    对于上面这个有效对齐值的计算有个网友总结得很好,可以参考:

    使用#pragma pack(n) 设定对齐系数分为两种情况:第一、如果n大于等于该成员所占用的字节数,那么偏移量必须满足默认的对齐方式,即自然对齐方式。第二、如果n小于该成员的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必然为n的倍数。

    四、下面通过一些个人认为比较典型的例子进行巩固

    答案在后面,我的答案也不一定正确,可以先自己动手做然后上机验证后在来参考。

    例子1:
    struct Practice1
    {
      char _szA[123];
      int _iB;
      float _iC;
      double _dD;
    };
    1、 求出此结构体在默认情况下的大小,并将其内存布局通过图文并茂的方式描述清楚。
    2、 若在此结构体上之前加入#pragma pack(2),之后加上#pragma pack(),那么此时,其大小又为多少?

    例子2:

    struct Practice2
    {
      struct T1
      {
        char _cA;
        int _iB;
        float _fC;
       } _objX;
      int _iD;
      char _iE;
    };
    1、 求出此结构体在默认情况下的大小,并将其内存布局通过图文并茂的方式描述清楚。
    2、 若在此结构体上之前加入#pragma pack(4),之后加上#pragma pack(),那么此时,其大小又为多少?

    例子3:

    struct Practice3
    {
      union T1
      {
        char _cT;
        double _dT;
        int _iT;
      } _uT;
      int _iA;
      double _dB;
    };
    1、 求出此结构体在默认情况下的大小,并将其内存布局通过图文并茂的方式描述清楚。
    2、 若在此结构体上之前加入#pragma pack(1),之后加上#pragma pack(),那么此时,其大小又为多少?

    参考答案:

    分析:

    例子1:

    (1)默认对齐值为8,从0x0000开始,即 #pragma pack(8)
      char _szA[123]; 123+cc……cc=124
      int _iB; 124+4=128
      float _iC; 128+4 +cc +...+cc = 132 + 4cc = 136
      double _dD; 136+8=144
        (2)若#pragma pack(2)
    则内存分布为:123+cc+4+4+8=140

    例子2:

    (1)#pragma pack(8)

    struct T1
    {
      char _cA; 1+cc…+cc=4
      int _iB; 4+4=8
      float _fC; 8+4=12
    } _objX; 12
    int _iD; 12+4=16
    char _iE; 16+1 = 17+cc+…+cc = 20
    所以结果为:20

    (2)若#pragma pack(4)
    则:1+cc+cc+cc+4+4+4+1+cc+cc+cc=20

    例子3:

    (1)若#pragma pack(8)
    union T1
    {
      char _cT;
      double _dT; 8
      int _iT;
    } _uT; 8
    int _iA; 8+4+cc…+cc=16
    double _dB; 16+8 =24
    (2)若#pragma pack(1)
    则为:8+4+8=20

    验证:

     1 #include <iostream>
     2 using namespace std;
     3 
     4 struct Practice1
     5 {
     6     char _szA[123];
     7     int _iA;
     8     float _iB;
     9     double _iD;
    10 };
    11 
    12 struct Practice2
    13 {
    14     struct T1
    15     {
    16         char    _cA;
    17         int     _iB;
    18         float   _fC;
    19     }       _objX;
    20     int     _iD;
    21     char    _iE;
    22 };
    23 
    24 struct Practice3
    25 {
    26     union T1
    27     {
    28         char   _cT;
    29         double _dT;
    30         int    _iT;
    31     }   _uT;
    32     int     _iA;
    33     double  _dB;
    34 };
    35 
    36 int main(){
    37     cout << sizeof(Practice1) << endl;
    38     cout << sizeof(Practice2) << endl;
    39     cout << sizeof(Practice3) << endl;
    40     return 0;
    41 }

     1 #include <iostream>
     2 using namespace std;
     3 
     4 #pragma pack(2)
     5 struct Practice1
     6 {
     7     char _szA[123];
     8     int _iA;
     9     float _iB;
    10     double _iD;
    11 };
    12 #pragma pack()
    13 
    14 #pragma pack(4)
    15 struct Practice2
    16 {
    17     struct T1
    18     {
    19         char    _cA;
    20         int     _iB;
    21         float   _fC;
    22     }       _objX;
    23     int     _iD;
    24     char    _iE;
    25 };
    26 #pragma pack()
    27 
    28 #pragma pack(1)
    29 struct Practice3
    30 {
    31     union T1
    32     {
    33         char   _cT;
    34         double _dT;
    35         int    _iT;
    36     }   _uT;
    37     int     _iA;
    38     double  _dB;
    39 };
    40 #pragma pack()
    41 
    42 int main(){
    43     cout << sizeof(Practice1) << endl;
    44     cout << sizeof(Practice2) << endl;
    45     cout << sizeof(Practice3) << endl;
    46     return 0;
    47 }

    OK,祝君学习愉快!

    转载请注出处:http://www.cnblogs.com/bakari/archive/2012/08/27/2658956.html


    我的公众号 「Linux云计算网络」(id: cloud_dev),号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,分享的内容包括但不限于 Linux、网络、云计算虚拟化、容器Docker、OpenStack、Kubernetes、工具、SDN、OVS、DPDK、Go、Python、C/C++编程技术等内容,欢迎大家关注。

    stay hungry stay foolish ----jobs 希望多多烧香!
  • 相关阅读:
    12-14面向对象--抽象基类、接口、委托
    关于 try catch catch
    C# using 三种使用方式
    互斥锁(Mutex)
    C#中Monitor类、Lock关键字和Mutex类
    System.Data.SQLite
    Dictionary<TKey, TValue> 类
    AttributeTargets 枚举
    C# is和as操作符
    合并委托(多路广播委托)
  • 原文地址:https://www.cnblogs.com/bakari/p/2658956.html
Copyright © 2020-2023  润新知