您的位置:首页 > 其它

结构体对齐问题以及强制类型转换问题总结

2016-06-27 08:40 483 查看

一、什么是对齐

    现在使用的计算机中内存空间都是按照字节划分的,从理论上讲似乎对任何类型的变量的访问都可以从任何地址开始,但实际上计算机系统对于基本数据类型在内存中的存放位置都有限制。举个例子,一个变量占用n个字节,则该变量的起始地址必须能够被n整除,即存放起始地址%n = 0。各种基本数据结构类型在内存中是按照这种规则排列,而不是一个紧接着一个排列的。这就是内存对齐。

    对结构体而言,存储的起始地址必须能被其成员中的数据类型占空间的值最大的那个整除。

二、对齐的作用和原因

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

三、对齐的原则

    在不使用#pragma pack宏的情况下,结构体的对齐原则有如下几个:

   原则一、数据成员按数据类型自身的对齐值对齐:结构的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始。

   原则二、结构体中有某些结构体成员,则结构体成员要按自身结构体内部最大对齐值进行对齐。一个简单的例子:struct A中包含struct B类型的成员,B中有char、int、double元素,那么B应该从sizeof(double)的整数倍开始存储。

   原则三、结构体的自身对齐值是其成员中自身对齐值最大的那个值。即结构体的总大小,必须是其内部最大成员的整数倍,不足的要补齐。

    下面通过几个例子,加深对这几个原则的理解:(32位系统,gcc编译器)

[cpp] view
plain copy

struct A{  

  int a1;  

  int a2;  

};  

struct B{  

  char b1;  

  int b2;  

};  

struct C{  

  short c1;  

  char c2;  

  long c3;  

};  

struct D{  

  short d1;  

  long d2;  

  char d3;  

};  

struct E{  

  char e1;  

  struct C e2;  

  short e3;  

};  

    sizeof(A)=8, 这个很好理解,两个int都是4;

    sizeof(B)=8,这要大于char和int类型的和,原因就是原则1( 若struct B中b1和b2的定义换一下位置,sizeof(B)依然会等于8,但这时的原因就是原则3了)。

    sizeof(C)=8,而拥有同样多的并且同样类型的成员的struct D,其sizeof(D)=12。C和D的区别就是两个结构体中定义成员变量的顺序不一样,这导致了两者占用内存的大小不一样。原因是struct C中按原则1进行对齐后,刚好满足原则3;而struct D中将char型变量放最后,按原则1进行对齐后,并不满足原则3,需要按照原则3进行补齐,所以占用了额外的空间。两者的内存占用情况如下图:

               c1              c2               c3           

C的内存布局    1       1       1       *        1       1       1       1

               d1                               d2                              d3    

D的内存布局    1       1       *       *        1       1       1       1       1       *       *       *

    其中*表示填充的字节。比较两个结构体的内存布局,可以看出C的成员c2刚好填充在c3起始地址之前的空段中,而d3则没有利用那个可以容下自身的空段。所以通常结构体中成员变量声明的顺序是按照成员类型大小从小到大的顺序进行,有时候这样可以减少中间的填充空间。

    sizeof(E)=16,这是利用了原则2,其中的成员结构体e2,需要按照C的对齐值4对齐,所以从内存偏移为4的地方开始存储。这里注意E的对齐值是4(这个4是E中对齐值最大的成员e2的对齐值),三个成员存储后占用的空间不是4的倍数,所以最后填充两个字节,得到16。这里还是没按照成员类型大小从小到大的顺序进行声明,从而占用了更多的空间。将e2和e3换一下位置,sizeof(E)=12。

四、使用#pragma pack宏时的对齐原则

    #pragma pack(value)宏指令,value就是指定的对齐值,通常value的值取2的较小次方。

    如果value的值小于变量类型的对齐值,则按照value的值进行对齐。例如,取value=1,上述5个结构体所占空间大小分别是8,5,7,7,10。

    如果value的值大于变量类型的对齐值,则按照原来的对齐值进行对齐。例如,取value=8,上述5个结构体所占空间大小分别是8,8,8,12,16。与不使用该宏时的值一样。

    总之,使用该宏的时候,按照value值和原来对齐值之间较小的值进行对齐。

五、结构体强制类型转换

    直接看例子

[cpp] view
plain copy

#include<stdio.h>  

#include<stdlib.h>  

  

typedef struct {  

  int a;  

  int b;  

}A;  

typedef struct {  

  A test;  

  int c;  

  int d;  

}B;  

  

int main()  

{  

  A p;  

  A *pa;  

  B q;  

  B *qb;  

  

  p.a = 1;  

  p.b = 2;  

  q.c = 3;  

  q.d = 4;  

  pa = &p;  

  qb = &q;  

  qb = (B *)pa;  

    

  printf("%d %d %d %d\n", qb->test.a, qb->test.b, qb->c, qb->d);  

  return 0;  

}  

    (1)输出结果为1 2 -1077842928 -1077842928。

    (2)qb = (B *)pa改成qb->test = p; 其他不变,输出结果为1 2 3 4。

    (3)将struct B中的成员test和c换个位置,其他不变,输出结果为2 -1077983296 1 -1077983296。

结果分析:

    (1)结构体类型转换,比较两个结构的地址空间。(pa的地址空间不包含图中写未知的部分,为了比较所以这么画)

            pa(A)                qb(B)

     -----------------       -------------------

             a          

     -----------------              test

             b       

     -----------------       --------------------

            未知                     c

     -----------------       ---------------------

            未知                     d

     ------------------      ---------------------

     将pa的类型去套qb指向的地址空间,由于前两个变量类型对应上,而对应于qb的后两个变量在pa中没有定义,直接读取紧接其后的空间(不是由pa管理,得到的值是不确定的,多次执行也会发现后面两个变量输出结果不一样)。

    (2)对此输出结果不做说明,平时都是这么使用的。

    (3)同样采用(1)中的分析方法,比较地址空间

            pa(A)                qb(B)

     -----------------       -------------------

             a                       c

     -----------------       --------------------

             b       

     -----------------              test

            未知                     

     -----------------       ---------------------

            未知                     d

     ------------------      ---------------------
    注意输出顺序,qb->test.a对应着pa->b,结果为2;qb->test.b对应着未知位置,所以输出不确定的值;qb->c对应着pa->a,结果为1;qb->d对应着未知位置,所以输出不确定的值。

    结论:对于结构体的强制类型转换,分析对应的地址空间,可以得到结果。本例中所有变量类型都用int,分析起来简单的多,如果类型不一样,要根据各种类型的表示方法才能得到输出结果(通常不是预期的)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: