字节对齐_字节对齐详解 - CSDN
字节对齐 订阅
字节(Byte)是计算机信息技术用于计量存储容量和传输容量的一种计量单位,一个字节等于8位二进制数,在UTF-8编码中,一个英文字符等于一个字节。字节按照一定规则在空间上排列就是字节对齐。 展开全文
字节(Byte)是计算机信息技术用于计量存储容量和传输容量的一种计量单位,一个字节等于8位二进制数,在UTF-8编码中,一个英文字符等于一个字节。字节按照一定规则在空间上排列就是字节对齐。
信息
外文名
Byte aligned
拼    音
zi jie dui qi
相关领域
计算机信息技术
释    义
字节按照一定规则在空间上排列
中文名
字节对齐
性    质
一种计量单位
字节对齐解释
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
收起全文
精华内容
参与话题
  • C/C++ 字节对齐

    千次阅读 2016-06-23 08:39:23
    字节对齐 结构体的字节对齐是我很早就想全面了解一下的东西,这个东西本质上是和硬件相关的,本来要想真正全面了解的话必须得知道CPU的结构、内存的结构、CPU指令是如何执行的等这些硬件层的东西才行,并不是说了解...


    结构体的字节对齐是我很早就想全面了解一下的东西,这个东西本质上是和硬件相关的,本来要想真正全面了解的话必须得知道CPU的结构、内存的结构、CPU指令是如何执行的等这些硬件层的东西才行,并不是说了解几个寄存器写两句汇编码就可以的,和这个没联系。在尝试了几次之后,迫于个人能力问题,无奈之下只好放弃深层次的了解,只能了解一下字节对齐的规则,至于为什么要对齐的话题,大概也就只能从网络上那几句肤浅的话了解到了:


    “一些平台对某些特定类型的数据只能从某些特定地址开始存 ……最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失……”


    只是想了解字节对齐的规则也不是想象中那么容易的事情,对齐规则本身并不算复杂,而最困惑的是网上各种错误的文章,至少我看过的那几十篇包括一些重复的转载和引用,基本上是错的。当然,也并不是说他们错得多么的离谱,大致还是对的,只是一些困惑的地方从来没有人去证实,都自然而然的想当然了,而结果却是错误的。

    所以我还是那句话,从来都不带怀疑和思考的精神去看别人的东西,从来不经过自己的证实就轻易相信,那么,误导了你也是你自己活该~~

    我写这篇笔记,首先是要证明网上那些错误的地方,以及给出我自己的一些结论。然后就是给出我自己总结出来的一个字节对齐规则的版本。

     

    1.  证明它是错的


    我所说的他们那些错误的地方,基本上可以归结于一个问题上的错误,那就是对变量的起始地址的假设。


    很多文章大概都有像这样的结论:


    1. 数据项只能存储在地址是数据项大小的整数倍的内存位置上;

    2. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

    3. 对齐在N上,也就是说该数据的"存放起始地址%N=0


    很明显,如果对数据存放地址的把握错误了的话,那么由此推断出来的地址对齐规则也就全都是错的了,而事实上也是如此。我研究这个课题 80%的时间都是花在这个上面,而真正的对齐规则一个下午应该就可以解决了。


    当然,像上面的结论在一般情况下基本上是正确的,也就是说:


    char变量的地址 %1 =0;

    short变量地址 %2 =0;

    int变量的地址 %4 =0;

    double变量的地址 %8 =0


    这里假设:


    sizeof(char) = 1;

    sizeof(short) = 2;

    sizeof(int)=4;

    sizeof(double)=8


    这是 win32 平台上的实际值,此篇都以此假设为基础。


    当这些变量是处于内存的数据区(或只读数据区)或者是从堆上分配出来的话,应该都是正确的,因为编译器和堆管理可能会帮你把这件事情做得很好,而程序员在代码里面基本上控制不了这些区域的变量的起始地址,实则我的多次实际测试也都符合上面的结论,即变量存放起始地址%N=0,结构体也符合首地址能够被其最宽基本类型成员的大小所整除。


    而唯一我们比较好灵活控制的就是栈上数据,也就是局部变量。在 32 位的系统上栈的单位大小是 32 bit,即 4 字节,每一个栈的地址肯定也是 %4 = 0 的,如果一个栈存放了 char / short / int 的话那么他们肯定也满足 % N = 0。我们唯一可以找到破绽的就是使用一个8字节的数据,也就是 double,通过对栈上数据的巧妙安排让 double 变量的地址处于一个可以让 4 整除而不可以让 8 整除的地址上,那么我们的目的就达到了,"存放起始地址 % N = 0 "的结论即可推翻。当然,熟悉栈布局的话这些是可以轻易做到的。具体可以按如下步骤实验。


    首先编译这个代码看

    #include "stdafx.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
    	double a = 0;
    
    	double b = 0;
    
    	double c = 0;
    
    	printf("&a=0x%X, &b=0x%X, &c=0x%X", &a, &b, &c);
    	getchar();
    	return 0;
    }




    以下是 VS2013 上的输出


     

    可以看到,变量地址的最后一位是 4,不能被 8 整除(double 为 8 字节),因此已经可以推翻结论1了。


    得到的这些地址可能有些随机性,这些地址依赖于具体的环境和编译参数等,但是,无论如何,我们实际看到的东西已经可以推翻上面结论 1 对地址假设的错误的结论了。我们再来看结构体的情况,把他们彻底推翻!为了让内部类型的变量一定可以按照其自身的对齐参数对齐,我们指定对齐参数设为16。   

    #include "stdafx.h"
    
    #pragma pack(16)
    
    struct A{
    
    	char a;
    
    	double b;
    
    };
    
    
    
    int _tmain(int argc, _TCHAR* argv[]){
    
    	struct A a;
    
    	struct A b;
    
    	struct A c;
    
    	printf("&a=0x%X, &b=0x%X, &c=0x%X\n", &a, &b, &c);
    
    	printf("&a.b=0x%X, &b.b=0x%X, &c.b=0x%X", &a.b, &b.b, &c.b);
    
    	getchar();
    	return 1;
    
    }



    VS2013输出的结果:

     


    由上面可以看到,结构体 A 的最大长度成员的类型是 double,就是成员变量 b 。 可以看到,结构体变量的起始地址不能被8整除,结构体中的 double 成员的地址也不能被 8 整除,当然这个的原因还是结构体变量的起始地址不能被8整除导致 double 的成员也不能被 8 整除。


    我们实际看到的东西已经可以推翻上面结论 2 对地址假设的错误的结论了


    尽管在不同的编译环境和系统上会有不同的值,但是从上面的两个实验我们确实得到了不满足那些结论的值,也就是说,无论如何,那些对变量起始地址的假设和结论一定错了~!


    我本来以为我对栈布局还比较熟悉,通过多次试验是不是可以完全预测,但是后来证明这些一切都是徒劳,在不同的编译环境下,特别是编译器的优化选项,栈的存放简直就是五花八门,根本预测不到,我们确实不能对变量的地址做过多的假设。 

     

    2.  地址的字节对齐规则

    地址对齐的规则也不是很复杂,只要把对起始地址有假设的那些结论稍微改一下基本上就差不多了。以下我先给出我自己总结的一个版本,然后再慢慢论证与解析。


    编译器都有一个指定的对齐参数用于 structure, union, and class 成员,在 win32 平台上的编译器都是默认为 8,这个指定的对齐参数可以在代码里面使用 pack(n) 指令指定,n合法的值是1,2,4,8,16。


    每个内部类型自身也都有一个自己的对齐参数,一般来说这个对齐参数就是 sizeof(具体type) 的值,在 win32 平台上就是采用sizeof作为具体类型的自身对齐参数的,也就是讲,char 的自身对齐参数是 1 , short 是 2 , int 是 4, float 也是 4, double 是 8 等。


    地址对齐是相对于结构的成员来说的,单个内部类型的变量这种就没什么对齐不对齐的说法了。


    结构的成员按照结构中声明的顺序依次排放,对齐的意思是成员相对于结构变量的起始地址的相对对齐,关键是在于相对于结构变量的起始地址的偏移。


    有效对齐参数,内部类型的有效对齐是指它的自身对齐参数和指定对齐参数中较小的那个对齐参数;结构类型的有效对齐参数是指它的成员中,有效对齐参数最大的那个值。数组的有效对齐就是它的成员类型的有效对齐。


    有了这些就可以得出对齐规则了:


    1.   (成员的起始地址相对于结构的起始地址的偏移) % (成员的有效对齐) == 0

    2.   (结构的总大小) % (结构的有效对齐) == 0

    3.   如果无法满足对齐规则的话就填充字节直到满足对齐规则 


    从上面可以看到,如果指定的对齐参数大于了变量的自身对齐参数的话,指定的对齐参数将不起作用,这就是之前为什么要 #pragma pack(16) 的原因了,使得指定对齐参数没用,各个变量按照自己的类型的自身对齐参数对齐。


    结构的总大小也要求符合对齐规则,主要是考虑到了结构体数组的情况,数组的各个成员是紧密排列的,不会有空隙,如果结构总大小满足对齐要求的话那么整个数组就自然满足对齐要求了,如果总大小不满足对齐要求的话,数组各个成员又要紧密排列,那么这个对齐就又没意义了,CPU 读取这些数组成员还是要花多余的开销。


    说了这么多,还是举例子讲话来得实在。

     

    # pragma pack(16)
    struct A{
          char a;
          double b;
    };


     

    第一个成员的地址就是结构的起始地址,所以它的地址相对于结构的起始地址的偏移是 0,而 a 是 char 类型,它的自身对齐是 1 小于指定的对齐参数 16,所以 a 的有效对齐是1,a 的起始地址偏移也满足 0 % 1 = 0;第二个成员是 double 类型,其自身对齐参数是 8,也小于指定的对齐参数,所以它的有效对齐是 8,这样我们指定的 # pragma pack(16) 就相当于一点用都没有了。而 double 类型的成员 b 要想满足对齐规则就必须在 a 的后面填充字节以使得 b 的地址相对于结构的起始地址的偏移至少为 8。所以结构 A的内存布局会是这样:

    00 CC CC CC CC CCCC CC 00 00 00 00 00 00 00 00  


    而 sizeof(A) = 16;0 分别表示 a 和 b 的位置,CC就是填充的 7 个字节。

     

    # pragma pack(16)
    struct A{
          char a;
          short c;
          double b;
    };


     

    同样指定的对齐参数仍然没任何作用,a 还是在偏移为 0 的地址上,c 在 a 之后,c 的有效对齐就是自身对齐 2,位于相对起始地址偏移为 2 的地址上,满足对齐要求 2 % 2 = 0,c 和 a 之间填充了 1 个字节。b  仍然位于偏移地址为 8 的地址上,b 和 c 之间填充了 4 个字节。结构 A 的内存布局如下:

    00 CC 00 00 CC CCCC CC 00 00 00 00 00 00 00 00  


    而 sizeof(A) = 16;0 分别代表 a,c,b的位置,CC 就是填充字节。

     

    # pragma pack(16)
    struct A{
          char a;
          double b;
          short c;
    };


     

    指定对齐仍然没用,a 在偏移为 0 的地址上,b 在偏移为 8 的地址上,c 紧紧挨着 b 的屁股,因为此时的地址偏移 16 已经满足 c 的对齐要求 16 % 2 = 0;所以就没必要填充字节了。但是结构体 A 的总大小也要满足对齐规则的第二条,即 (结构的总大小)%(结构的有效对齐) == 0;而结构 A 的有效对齐就是各个成员中有效对齐最大的那个数,也就是 b的对齐参数 8,所以 A 的有效对齐就是 8,结构的总大小要满足对齐要求还必须在 c 后面填充 6 个字节。此时 A 的内存布局如下:

    00 CC CC CC CC CCCC CC 00 00 00 00 00 00 00 00 11 11 CC CC CC CC CC CC


    而 sizeof(A) = 24;0 分别代表 a,b 的位置,1111 代表 c 的位置。

     

    # pragma pack(4)
    struct A{
          char a;
          double b;
          short c;
    };


     

    把指定对齐参数设置成 4,此时 a 和 c 的有效对齐仍然是其自身对齐,而 b 因为它的自身对齐 8 大于了指定的对齐 4,所以 b 的有效对齐现在变成了 4 而不再是 8 了。a 仍然位于偏移 0,b 要满足对齐规则的话,地址偏移必须是其有效对齐的整数倍,所以 b 的偏移应该是 4,c 仍然紧紧跟在 b 的后面,因为此时的偏移 12 满足了 c 的对齐要求12 % 2 = 0;结构 A 的有效对齐现在也变成了 4,即等于成员中最大的有效对齐,b 的有效对齐。A 的总大小要满足对齐规则的话还必须在 c 的后面填充 2 个字节,让总大小变为 16 字节。此时 A 的内存布局如下:

    00 CC CC CC 00 0000 00 00 00 00 00 11 11 CC CC


    而 sizeof(A)  = 16;0 分别代表 a,b 的位置,1111 代表c的位置。

     

    # pragma pack(8)
    struct A{
          char a;
          double b;
    };
    struct B{
    int i;
       struct A sa;
    int c;
    };


     

    我们来看结构 B 的布局,把指定对齐参数设置成 8,其实也还是没起作用,我们最大的内部类型就是 8 的 double 了,刚好和指定的对齐参数相等。第一个成员 i  肯定是位于偏移为 0 的地址上了。然后第二个成员是一个结构成员,我们要找到这个成员的有效对齐参数,结构的有效对齐参数是其成员中最大的那个对齐参数,对于结构 A 来说就是 b 的对齐参数 8,所以 A 的有效对齐是 8。结构成员 sa 要想满足对齐要求,即 偏移 % 有效对齐 8 = 0;它的地址偏移应该为 8。所以 sa 和 i 之间需要填充 4 个字节。成员 c 仍然紧紧跟在 sa 后面,因为 sa 占 16 字节,此时的地址偏移 24 已经可以满足 c 的对齐要求 24 % 4 = 0 ;而结构 B 的总大小也要满足对齐规则,B 的有效对齐就是成员中最大的,sa的有效对齐 8。所以 B 的总大小要能被 8 整除,就必须在 c 的后面再填充 4 个字节。此时结构 B 的内存布局如下:

    00 00 00 00 CC CCCC CC 00 CC CC CC CC CC CC CC 00 00 00 00 00 00 00 00 11 11 11 11 CC CC CC CC


    而 sizeof(A) = 32;0 分别代表 i,sa.a,sa.b 的位置,11111111代表 c 的位置。

     

    # pragma pack(8)
    struct A{
          char a;
          double b;
    };
    struct B{
    int i;
    int c;
       struct A sa;
    };


     

    把 c 移到 sa 的上面,这样就不需要填充任何字节了。B的所有的成员刚好满足对齐规则。注意,结构 A 中的 b 和 a 之间还是要填充字节的,它内部要满足自己的对齐要求。此时 B 的内存布局如下:

    00 00 00 00 11 1111 11 00 CC CC CC CC CC CC CC 00 00 00 00 00 00 00 00


    而 sizeof(A) = 24;0分别代表 i ,sa.a,sa.b的位置,11111111 代表 c 的位置。

     

    # pragma pack(4)
    struct A{
          char a[9];
          double b;
    char c[29];
    int d[7];
    };


     

    数组成员 a 的有效对齐是和其成员类型 char 一样,1。成员 b 的有效对齐是指定的对齐参数 4,因为指定的比它自身的小。c 数组同样也是 1 字节对齐,数组 d 是 4 字节对齐,指定的对齐和它自身的对齐一样,都是 4。数组的各个成员是紧密排列的,所以,b 和 a 之间填充了 3 个字节,c 和 b之间不填充字节,d 和 c 之间填充3个字节,c 之后不填充字节。sizeof(A) = 80;

     

    然后基本上就这么多了,一些复杂的例子按照对齐规则,慢慢的找到各个有效对齐参数,都是可以迎刃而解的。联合体和类都是差不多的。还有一些位域的结构的话道理也是一样的,你知道在你自己的系统上位域是怎么安排的就可以了,对齐规则还是一样。下面是我随便写的一个,在 Windows 上的布局,可以参考下。注释有字节的占用大小,+ 表示有填充。

     

    # pragma pack(4)
    struct A{
       char a[5];  //5+1
       short b:2;  // 2
       int c:5;    // 4
       int :8;     //
       int d:20;   // 4
       int :0;     //
       int e:8;    // 4
       short f:1;  // 2
       short g;    // 2
       short h:5;  // 2+2
       double i;   // 8
       char j:2;   // 1+3
    };
       sizeof(A) = 40;


     

     

    展开全文
  • C语言字节对齐详解

    万次阅读 多人点赞 2018-03-22 20:44:51
    C语言字节对齐12345不同系统下的C语言类型长度 Data Type ILP32 ILP64 LP64 LLP64 char 8 8 8 8 short 16 16 16 16 int 32 64 32 32 long 32 64 64 32 long long 64 64 64

    C语言字节对齐12345

    不同系统下的C语言类型长度

    Data Type ILP32 ILP64 LP64 LLP64
    char 8 8 8 8
    short 16 16 16 16
    int 32 64 32 32
    long 32 64 64 32
    long long 64 64 64 64
    pointer 32 64 64 64

    绝大部分64位的Unix,linux都是使用的LP64模型;32位Linux系统是ILP32模型;64位的Windows使用的是LLP64(long long and point 64)模型。

    基本概念

    许多计算机系统对基本数据类型合法地址做出了一些限制,要求某种类型对象的地址必须是某个值K(通常是2,4或8)的倍数。这种对齐限制简化了形成处理器和存储器系统之间的接口的硬件设计。对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。

    为什么要字节对齐

    需要字节对齐的根本原因在于CPU访问数据的效率问题。例如,假设一个处理器总是从存储器中取出8个字节,则地址必须为8的倍数。如果我们能保证将所有的double类型数据的地址对齐成8的倍数,那么就可以用一个存储器操作来读或者写值了。否则,我们可能需要执行两次存储器访问,因为对象可能被分放在两个8字节存储块中。

    另外,假设一个整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据;如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。

    各个硬件平台对存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐。比如sparc系统,如果取未对齐的数据会发生错误,举个例:

    char ch[8];
    char *p = &ch[1];
    int i = *(int *)p;

    运行时会报segment error,而在x86上就不会出现错误,只是效率下降。

    如何处理字节对齐

    先让我们看编译器是按照什么样的原则进行对齐的:

    1. 数据类型自身的对齐值:为指定平台上基本类型的长度。对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
    2. 结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
    3. 指定对齐值:#pragma pack (value)时的指定对齐值value。
    4. 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

    对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
    数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
    联合 :按其包含的长度最大的数据类型对齐。
    结构体: 结构体中每个数据类型都要对齐。

    当数据类型为结构体时,编译器可能需要在结构体字段的分配中插入间隙,以保证每个结构元素都满足它的对齐要求。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放(对于非对齐成员需要在其前面填充一些字节,保证其在对齐位置上),结构体本身也要根据自身的有效对齐值圆整(就是结构体总长度需要是结构体有效对齐值的整数倍),此时可能需要在结构末尾填充一些空间,以满足结构体整体的对齐—-向结构体元素中最大的元素对齐。

    通过上面的分析,对结构体进行字节对齐,我们需要知道四个值:

    • 指定对齐值:代码中指定的对齐值,记为packLen;
    • 默认对齐值:结构体中每个数据成员及结构体本身都有默认对齐值,记为defaultLen;
    • 成员偏移量:即相对于结构体起始位置的长度,记为offset;
    • 成员长度:结构体中每个数据成员的长度(注结构体成员为补齐之后的长度),记为memberLen。

    及两个规则:

    1. 对齐规则: offset % vaildLen = 0,其中vaildLen为有效对齐值vaildLen = min(packLen, defaultLen)
    2. 填充规则: 如成员变量不遵守对齐规则,则需要对其补齐;在其前面填充一些字节保证该成员对齐。需填充的字节数记为pad

    Linux和Microsoft Windows的对齐方式

    一.Linux的对齐策略:

    在Linux中2字节数据类型(例如short)的地址必须是2的倍数,而较大的数据类型(例如int,int *,float和double)的地址必须是4的倍数。也就是说Linux下要么2字节对齐,要么4字节对齐,没有其他格式的对齐。

    二.Microsoft Windows的对齐策略:

    在Windows中对齐要求更严–任何K字节基本对象的地址都必须是K的倍数,K=2,4,或者8.特别地,double或者long long类型数据的地址应该是8的倍数。可以看出Windows的对齐策略和Linux还是不同的。

    更改C编译器的缺省字节对齐方式

    在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:

    • 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
    • 使用伪指令#pragma pack (),取消自定义字节对齐方式。

    另外,还有如下的一种方式:

    • __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
    • __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

    字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

    对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。

    什么时候需要设置对齐

    在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐。即使看起来本来就自然对齐的也要使其对齐,以免不同的编译器生成的代码不一样.

    结构体举例

    例子1

    /************************
        > File Name: struct_test.c
        > Author:Marvin
        > Created Time: Thu 22 Mar 2018 07:19:46 PM CST
     **********************/
    
    #include<stdio.h>
    
    
    int main()
    {
        struct test {
            char a;
            short b;
            int c;
            long d;
        };
        struct test t = {'a',11,11,11};
    
        printf("size of struct t = %u\n", sizeof(t));
    
        return 0;
    }
    

    在64位centos上编译编译后结构struct test的布局如下:

    struct_ex1

    由于要保证结构体每个元素都要数据对齐,因此必须在a和b之间插入1字节的间隙使得后面的short元素2字节对齐int元素4字节对齐long元素8字节对齐,这样最终test结构大小为16字节。

    运行程序结果为:

    size of struct t = 16

    例子2

    现在考虑这样一个结构体:

    struct test2 {
        int a;
        long b;
        char c;
    };
    struct test2 t2 = {11,11,'c'};

    在64位centos上编译编译后结构struct test2的布局如下:

    struct_ex2

    结构体struct test2的自然对界条件为8字节,所以需要在最后的char型数据后面再填充7个字节使得结构体整体对齐。

    运行程序结构为

    size of struct test2 = 24

    例子3

    不妨将结构体struct test2里面成员的顺序重新排列一下:

    struct test3 {
        char c;
        int a;
        long b;
    };
    struct test3 t3 = {'c',11,11};

    在64位centos上编译编译后结构struct test2的布局如下:

    struct_ex3

    运行结果为:

    size of struct test3 = 16

    可见适当地编排结构体成员地顺序,可以在保存相同信息地情况下尽可能节约内存空间。

    例子4

    struct B
    {
    char b;
    int a;
    short c;
    };

    假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
    例子5

    #pragma pack (2) /*指定按2字节对齐*/
    struct C
    {
    char b;
    int a;
    short c;
    };
    #pragma pack () /*取消指定对齐,恢复缺省对齐*/

    第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

    对于结构体嵌套地情况,结构体对齐算法思想:深度优先填充。

    padLen = getPadLen(offset , defaultLen);
    int getPadLen(int offsetLen, int defaultLen)
    {
      int vaildLen = min(packLen,defaultLen);
      if(0 == vaildLen || 0 == offsetLen % vaildLen)
      {
        return 0;
      }
      return vaildLen - (offsetLen % vaildLen);
    }

    先对齐内层结构体:对每个数据成员计算其defaultLen、memberLen和offset;

    再遍历每个数据成员时计算:对于基本数据类型成员defaultLen=memberLen;对于结构体成员defaultLen等于它的所有成员的最大的memberLen;遍历时对成员的memberLen进行累加,得到当前成员的offsetLen;

    运用对齐及填充规则:在当前结构体成员前填充padLen个字节;

    下面是结构体作为成员的例子:

    struct test1 {
        int a;
        long b;
    };
    struct test4 {
        char a;
        struct test1 b;
        int c;
    };
    struct test4 t4 = {'a', {11,11},11}

    test1的内存分布:

    struct_ex6-1

    test4的内存分布:

    struct_ex6-2

    字节对齐可能带来的隐患

    代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:

    unsigned int i = 0x12345678;
    unsigned char *p=NULL;
    unsigned short *p1=NULL;
    
    p=&i;
    *p=0x00;
    p1=(unsigned short *)(p+1);
    *p1=0x0000;

    最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
    在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

    如何查找与字节对齐方面的问题

    如果出现对齐或者赋值问题首先查看

    1. 编译器的big little端设置
    2. 看这种体系本身是否支持非对齐访问
    3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作

    举例:

    #include<iostream>
    
    using namespace std;
    
    //windows 64 位默认 结构体对齐系数为8,32位 结构体对齐系数为4
    
    //测试系统对齐系数
    // #pragma pack(8)  my_struct_1 为16字节
    // #pragma pack(4)  my_struct_1 为12字节
    // 不加#pragma pack(8)  my_struct_1 为16字节
    //顾系统默认对齐系数为8
    
    struct my_struct_1
    {
        char a;     //1
        double b;   //之前补7 +8     8/8==1
    };
    
    #pragma pack(4)
    struct my_struct_2
    {
        char a;    //1
        double b;  //3+8
        int c;     //4     16/4=4
    };
    #pragma pack()
    
    #pragma pack(2)
    struct my_struct_3
    {
        char a;    //1
        double b;  //1+8
        int c;     //4     14/2
    };
    #pragma pack()
    
    #pragma pack(4)
    struct my_struct_4
    {
        char a[5];  //5
        double b;   //3+8   16/4
    };
    #pragma pack()
    
    #pragma pack(2)
    struct my_struct_5
    {
        char a[5];  //5
        double b;   //1+8   14/2
    };
    #pragma pack()
    
    #pragma pack(4)
    struct my_struct_6
    {
        char a;    //1
        char b[3]; //3
        char c;    //1   1+3+1
    };
    #pragma pack()
    
    #pragma pack(4)
    struct my_struct_7
    {
        char a;    //1
        char b[3]; //3
        char c;    //1   
        int d;     //补齐 3 +4 
    };
    #pragma pack()
    
    #pragma pack(4)
    struct test
    {
    char x1;   //1
    short x2;  //补齐1+ 2
    float x3;  //4
    char x4;   //1 补齐+3  
    };
    #pragma pack()
    
    int main()
    {
        cout<<"char:"<<sizeof(char)<<endl;
        cout<<"short:"<<sizeof(short)<<endl;
        cout<<"int:"<<sizeof(int)<<endl;
        cout<<"long:"<<sizeof(long)<<endl;
        cout<<"float:"<<sizeof(float)<<endl;
        cout<<"double:"<<sizeof(double)<<endl;
        cout<<"long double:"<<sizeof(long double)<<endl;
    
        cout<<sizeof(my_struct_1)<<endl;//8
        cout<<sizeof(my_struct_2)<<endl;//16
        cout<<sizeof(my_struct_3)<<endl;//14
        cout<<sizeof(my_struct_4)<<endl;//16
        cout<<sizeof(my_struct_5)<<endl;//14
        cout<<sizeof(my_struct_6)<<endl;//5
        cout<<sizeof(my_struct_7)<<endl;//12
    
        cout<<sizeof(test)<<endl;//12
    
        system("pause");
        return 0;
    }
    展开全文
  • C++ 字节对齐的总结(原因和作用)

    万次阅读 2017-11-09 11:23:08
    什么是字节对齐  现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据...

    一、介绍

    什么是字节对齐

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

    字节对齐的原因和作用

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


    二、请看下面的结构

    struct MyStruct 
    { 
    double dda1; 
    char dda; 
    int type 
    }; 


    对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求: 
      sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13 
    但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗? 
    其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。

    常用类型的对齐方式(vc6.0,32位系统)

    类型 
    对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量) 

    Char 
    偏移量必须为sizeof(char)即1的倍数 
    int 
    偏移量必须为sizeof(int)即4的倍数 
    float 
    偏移量必须为sizeof(float)即4的倍数 
    double 
    偏移量必须为sizeof(double)即8的倍数 
    Short 
    偏移量必须为sizeof(short)即2的倍数 


      各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。 
    下面用前面的例子来说明VC到底怎么样来存放结构的。 

    struct MyStruct 
    { 
    double dda1; 
    char dda; 
    int type 
    }; 


    为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,

    (1)先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;
    (2)接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;
    (3)接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的(4)地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;
    (5)这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。 


    下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况: 

    struct MyStruct 
    { 
    char dda; 
    double dda1; 
    int type 
    }; 


    这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明) 

    复制代码
    struct MyStruct 
    { 
    char dda;              //偏移量为0,满足对齐方式,dda占用1个字节; 
    double dda1;            //下一个可用的地址的偏移量为1,不是sizeof(double)=8的倍数,需要补足7个字节才能使偏移量变为8(满足对齐方式),因此VC自动填充7个字节,dda1存放在偏移量为8的地址上,它占用8个字节。 
    int type;             //下一个可用的地址的偏移量为16,是sizeof(int)=4的倍数,满足int的对齐方式,所以不需要VC自动填充,type存放在偏移量为16的地址上,它占用4个字节。 
    };                 //所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为sizeof(double)=8的倍数。 
    复制代码


    所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。 
    VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。 
    VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:

    (1)如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,
    (2)如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
    结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数(两者相比,取小。)

    下面举例说明其用法。 

    复制代码
    #pragma pack(push)                   //保存对齐状态 
    #pragma pack(4)                    //设定为4字节对齐 
    struct test 
    { 
    char m1; 
    double m4; 
    int m3; 
    }; 
    #pragma pack(pop)                   //恢复对齐状态 
    复制代码


    以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)

    三、再看下面这个例子

    复制代码
    #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的内存布局:1***,1111,
    c S1.a S1.b d
    S2的内存布局:1***,1***,1111,****11111111

    这里有三点很重要:
    (1)每个成员分别按自己的方式对齐,并能最小化长度。
    (2)复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。
    (3)对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。

    四、总结

    内存对齐计算方法:
    (1)看变量所在偏移地址是否为变量大小的整数倍
    (2)看对齐后的总大小是否为最长变量的整数倍

    展开全文
  • 为什么需要字节对齐

    万次阅读 多人点赞 2012-06-14 11:36:54
    文章最后本人做了一幅图,一看就明白了,这个问题网上讲的不少,但是都没有把问题说透。  一、概念    对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于... 需要字节对齐的根本原因在于CPU访问数

    文章最后本人做了一幅图,一看就明白了,这个问题网上讲的不少,但是都没有把问题说透。

      一、概念
      
       对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。
      
      二、为什么要字节对齐
      
       需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误,举个例:
      
      char ch[8];
      char *p = &ch[1];
      int i = *(int *)p;
      
      
      运行时会报segment error,而在x86上就不会出现错误,只是效率下降。
      
      三、正确处理字节对齐
      
       对于标准数据类型,它的地址只要是它的长度的整数倍就行了,而非标准数据类型按下面的原则对齐:
      
      数组 :按照基本数据类型对齐,第一个对齐了后面的自然也就对齐了。
      联合 :按其包含的长度最大的数据类型对齐。
      结构体: 结构体中每个数据类型都要对齐。
      比如有如下一个结构体:
      
      struct stu{
       char sex;
       int length;
       char name[10];
      };
      struct stu my_stu;
      
      
      由于在x86下,GCC默认按4字节对齐,它会在sex后面跟name后面分别填充三个和两个字节使length和整个结构体对齐。于是我们sizeof(my_stu)会得到长度为20,而不是15.
      
      四、__attribute__选项
      
      我们可以按照自己设定的对齐大小来编译程序,GNU使用__attribute__选项来设置,比如我们想让刚才的结构按一字节对齐,我们可以这样定义结构体
      
      struct stu{
       char sex;
       int length;
       char name[10];
      }__attribute__ ((aligned (1)));
      
      struct stu my_stu;
      
      
      则sizeof(my_stu)可以得到大小为15。
      
      上面的定义等同于
      
      struct stu{
       char sex;
       int length;
       char name[10];
      }__attribute__ ((packed));
      struct stu my_stu;
      
      
      __attribute__((packed))得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐.
      
      五、什么时候需要设置对齐
      
       在设计不同CPU下的通信协议时,或者编写硬件驱动程序时寄存器的结构这两个地方都需要按一字节对齐。即使看起来本来就自然对齐的也要使其对齐,以免不同的编译器生成的代码不一样.

     

    一、快速理解

    1. 什么是字节对齐?

    在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

    为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”. 比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除.

    2. 字节对齐有什么作用?

    字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。

    对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。

    3. 更改C编译器的缺省字节对齐方式

    在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:
    · 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
    · 使用伪指令#pragma pack (),取消自定义字节对齐方式。

    另外,还有如下的一种方式:
    · __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
    · __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

    4. 举例说明

    例1

    struct test
    {
    char x1;
    short x2;
    float x3;
    char x4;
    };

    由于编译器默认情况下会对这个struct作自然边界(有人说“自然对界”我觉得边界更顺口)对齐,结构的第一个成员x1,其偏移地址为0,占据了第1个字节。第二个成员x2为short类型,其起始地址必须2字节对界,因此,编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然边界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大边界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

    例2

    #pragma pack(1) //让编译器对这个结构作1字节对齐
    struct test
    {
    char x1;
    short x2;
    float x3;
    char x4;
    };
    #pragma pack() //取消1字节对齐,恢复为默认4字节对齐

    这时候sizeof(struct test)的值为8。

    例3

    #define GNUC_PACKED __attribute__((packed))
    struct PACKED test
    {
    char x1;
    short x2;
    float x3;
    char x4;
    }GNUC_PACKED;

    这时候sizeof(struct test)的值仍为8。

    二、深入理解

    什么是字节对齐,为什么要对齐?
    TragicJun 发表于 2006-9-18 9:41:00 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
          对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
    二.字节对齐对程序的影响:

            先让我们看几个例子吧(32bit,x86环境,gcc编译器):
    设结构体如下定义:
    struct A
    {
            int a;
            char b;
            short c;
    };
    struct B
    {
            char b;
            int a;
            short c;
    };
    现在已知32位机器上各种数据类型的长度如下:
    char:1(有符号无符号同)   
    short:2(有符号无符号同)   
    int:4(有符号无符号同)   
    long:4(有符号无符号同)   
    float:4        double:8
    那么上面两个结构大小如何呢?
    结果是:
    sizeof(strcut A)值为8
    sizeof(struct B)的值却是12

    结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7字节。
    之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
    #pragma pack (2) /*指定按2字节对齐*/
    struct C
    {
            char b;
            int a;
            short c;
    };
    #pragma pack () /*取消指定对齐,恢复缺省对齐*/
    sizeof(struct C)值是8。
    修改对齐值为1:
    #pragma pack (1) /*指定按1字节对齐*/
    struct D
    {
            char b;
            int a;
            short c;
    };
    #pragma pack () /*取消指定对齐,恢复缺省对齐*/
    sizeof(struct D)值为7。
    后面我们再讲解#pragma pack()的作用.

    三.编译器是按照什么样的原则进行对齐的?

            先让我们看四个重要的基本概念:


    1.数据类型自身的对齐值:
          对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
    2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
    3.指定对齐值:#pragma pack (value)时的指定对齐值value。
    4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
    有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
    例子分析:
    分析例子B;
    struct B
    {
            char b;
            int a;
            short c;
    };
    假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,复核0x0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
    同理,分析上面例子C:
    #pragma pack (2) /*指定按2字节对齐*/
    struct C
    {
            char b;
            int a;
            short c;
    };
    #pragma pack () /*取消指定对齐,恢复缺省对齐*/
    第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
    在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.

    四.如何修改编译器的默认对齐值?

    1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
    2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

    五.针对字节对齐,我们在编程中如何考虑?
            如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做法是显式的插入reserved成员:
                 struct A{
                   char a;
                   char reserved[3];//使用空间换时间
                   int b;
    }

    reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.

    六.字节对齐可能带来的隐患:

            代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
    unsigned int i = 0x12345678;
    unsigned char *p=NULL;
    unsigned short *p1=NULL;

    p=&i;
    *p=0x00;
    p1=(unsigned short *)(p+1);
    *p1=0x0000;
    最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
    在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

    七.如何查找与字节对齐方面的问题:

    如果出现对齐或者赋值问题首先查看
    1. 编译器的big little端设置
    2. 看这种体系本身是否支持非对齐访问
    3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作

    举例:

    1. #include <stdio.h>   
    2. main()  
    3. {  
    4. struct A {  
    5.     int a;  
    6.     char b;  
    7.     short c;  
    8. };  
    9.   
    10. struct B {  
    11.     char b;  
    12.     int a;  
    13.     short c;  
    14. };  
    15.   
    16. #pragma pack (2) /*指定按2字节对齐*/   
    17. struct C {  
    18.     char b;  
    19.     int a;  
    20.     short c;  
    21. };  
    22. #pragma pack () /*取消指定对齐,恢复缺省对齐*/   
    23.   
    24.   
    25.   
    26. #pragma pack (1) /*指定按1字节对齐*/   
    27. struct D {  
    28.     char b;  
    29.     int a;  
    30.     short c;  
    31. };  
    32. #pragma pack ()/*取消指定对齐,恢复缺省对齐*/   
    33.   
    34. int s1=sizeof(struct A);  
    35. int s2=sizeof(struct B);  
    36. int s3=sizeof(struct C);  
    37. int s4=sizeof(struct D);  
    38. printf("%d\n",s1);  
    39. printf("%d\n",s2);  
    40. printf("%d\n",s3);  
    41. printf("%d\n",s4);  
    42. }  


    输出:

    8

    12

    8

    7

     

    修改代码:

    struct A {
       // int a;
        char b;
        short c;
    };

    struct B {
        char b;
       // int a;
        short c;
    };

    输出:

    4

    4

    输出都是4,说明之前的int影响对齐!

    看图就明白了

    展开全文
  • 内存中的字节对齐

    千次阅读 2012-04-23 15:35:55
    一、什么是字节对齐,为什么要对齐?   现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就...
  • 概念  在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按...
  • 字节对齐

    千次阅读 2012-08-21 13:10:43
    在c++中字节对齐主要存在符合类型中:union,struct和class中 先介绍四个概念: 1)数据类型自身的对齐值:基本数据类型的自身对齐值,等于sizeof(基本数据类型)。 2)指定对齐值:#pragma pack (value)时的指定...
  • 关于字节对齐

    2020-10-12 10:56:42
    下图是从原文摘抄过来: 数据类型 16位编译器 32位编译器 64位编译器 char 1字节 1字节 1字节 ... 2字节 4字节 8字节 ... 2字节 2字节 2字节 ... 2字节 ... 4字节
  • 字节对齐的规则总结

    千次阅读 2017-09-15 14:05:33
    一、什么是字节对齐,为什么要对齐?现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要...
  • 关于C++中四字节对齐的坑

    千次阅读 2018-10-29 15:29:38
    最近做一个工程,大体的意思是在程序中定义一个结构,运行中会将结构直接写到文件中,然后另一个程序会用同样的结构读出来。为了验证是写文件的程序的问题还是读文件的程序的问题,用winhex来打开文件,仿照结构体...
  • C语言字节对齐

    万次阅读 多人点赞 2011-08-30 15:35:40
    文章最后本人做了一幅图,一看就... 一、概念 对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。比如在32位cpu下,假设一个整型变量的地址为0x00000004,那它就是
  • 结构体字节对齐

    千次阅读 2019-01-30 18:42:23
    结构体字节对齐
  • 嵌入式—字节对齐

    2016-07-15 23:44:46
    什么是字节对齐,为什么要对齐?  现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要...
  • C语言字节对齐、结构体对齐最详细的解释

    万次阅读 多人点赞 2015-03-17 22:10:26
    文章最后本人做了一幅图,一看就明白了,这个问题网上讲的不少,但是都没有把问题说透。  一、概念    对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于... 需要字节对齐的根本原因在于CPU访问数
  • 字节对齐、8字节对齐

    2020-04-20 14:10:02
    在内存管理中经常使用字节对齐来管理分配的内存。 1、原理 2字节对齐:要求地址位为2, 4, 6, 8…,地址的二进制最后一位为0(2的1次方)。 4字节对齐:要求地址位为4,8,12,16…,地址的二进制最后两位为0(2的2...
  • 什么是字节对齐,为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要...
  • 什么是字节对齐,为什么要对齐?  现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要...
  • Keil 字节对齐

    千次阅读 2019-06-17 22:37:07
    什么是字节对齐,为什么要对齐 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要...
  • http://blog.sina.com.cn/s/blog_14ecf1a5b0102x27j.html ... //-------------------------------------------------------- 本文目录结构 |-为什么结构体内存对齐 |-结构体内...
1 2 3 4 5 ... 20
收藏数 118,411
精华内容 47,364
关键字:

字节对齐