研究一下 c语言中 的 对齐 补齐

  本文编译环境:  win 7  64位 旗舰版   vs 2012 

 1)首先引用一下一道在 CSDN论坛 上讨论火热的题: 

 在研究问题前 先 摘要一段

各编译器下内存对齐的影响

上面那种是系统默认的对齐,也就是自然对齐。

现实中,编译器也能按指定的字节进行对齐,这称为指定对齐。如 VC , gcc

--------------------------------MSDN VC----------------------------------------------------

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

#pragma pack(n)

When you use #pragma pack(n), where n is 1, 2, 4, 8, or 16, each structure member after the first is stored on the smaller member type or n-byte boundaries. If you use #pragma pack without an argument, structure members are packed to the value specified by /Zp. The default /Zp packing size is /Zp8.

----------------------------------GCC-----------------------------------------------

__attribute__ ((packed));


#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

这道题目的答案 我运行了一下结果如下:

       但是 读者要注意 他的 对齐方式 说明c 后面空了 3个字节 来对齐d;;


2)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的对齐方式为成员中对齐方式最大的成员的对齐方式。

3) 注意: 

   1 ) 对于空结构体, sizeof == 1 ;因为必须保证结构体的每一个实例在内存中都 
有独一无二的地址。 
   2 ) 结构体的静态成员不对结构体的大小产生影响,因为静态变量的存储位置与 
结构体的实例地址无关。

   例如: 
struct {static int I;} T; struct {char a; static int I;} T1;
sizeof(T) == 1; sizeof(T1) == 1;

4)

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中才有这样的差异。
所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素

5)我想问的 问题   vs2012 的结果

  typedef  struct  

{

   int a;

   double c;

}s;

 sizeof(s)  ===???????  照例说 是 12 ? 但是 我的 编译结果 确 是 16????????

  别人vs2010下的 结果 也是16.。。。这个让我很纳闷  但是 有人引用的参考书上 确是12!

求解原因::::::????????????????????

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

再来看这段代码:

  

#include<stdio.h>
#include<iostream>

using namespace std;
struct s1
{
char a[8];
};
struct s2
{
double d;
};
struct s3
{
s1 s;
char a;
int c;
};
struct s4
{
s3 s;
char a;
double c;
};
void main() {
cout<<sizeof(s1)<<endl;//8
cout<<sizeof(s2)<<endl;//8
cout<<sizeof(s3)<<endl;//16
cout<<sizeof(s4)<<endl;//32;
system("pause");
} 
为什么 s4 是 32 呢 ??而不是24呢??是不是 我们 现在可以得出结论。呢?有double则按8字节对齐,而 没有的 则按 结构体中 最长字节的参数 进行对齐和补齐呢????

不再是什么大于四的按四算!!!!!!!!!!!!!!(这个是我老师讲的!)

那么 总结 他就是错的。。。double类型 大于四了 但是他就是按8字节算!!!

这个是在 centos 5.5下的 结果 真是让人难以置信!

所以说 这个 东西 还是取决于 编译器的。。。看他是按多少字节 算!



最后 补充一下 :

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

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

     · 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。

     · 使用伪指令#pragma pack (),取消自定义字节对齐方式。

 

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

     · __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。

     · __attribute__ ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

【转载例文】

1. 概述
    本文讨论了结构的自然边界对齐,在缺省情况下,c编译器为每一个变量或数据单元按其自然边界对齐条件分配空间。
    但可以通过四种方法来更改C编译器的缺省字节对齐方式,即可以指定边界对齐。
    
    在阅读完本文档后,将会更深入地了解一个结构的sizeof到底应当是多少。

2. 自然边界对齐
    在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。

    结构字节对齐有以下几个特点:
    1. 对于结构体,编译器会自动进行成员变量的对齐,以提高运算效率。
       缺省情况下,编译器为结构的每个成员按其自然边界对齐( natural alignment)条件分配空间。
       自然边界对齐即为默认对齐方式。

    2. 各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

    3. 结构整体的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个。

    4. 结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;
       也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;
       这样在处理数组时可以保证每一项都边界对齐;

    例如,下面的结构各成员空间分配情况:
    struct test
    {
        char  x1;
        short x2;
        float x3;
        char  x4;
    };
    结构的第一个成员x1,其偏移地址为0,占据了第1个字节。
    第二个成员x2为short类型,其起始地址必须2字节边界对齐,因此,编译器在x2和x1之间填充了一个空字节。
    结构的第三个成员x3和第四个成员x4恰好落在其自然边界对齐地址上,在它们前面不需要额外的填充字节。
    在test结构中,成员x3要求4字节边界对齐,是该结构所有成员中要求的最大边界对齐单元,因而test结构的自然边界对齐条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。

3. 指定边界对齐 
   在缺省情况下,c编译器为每一个变量或数据单元按其自然边界对齐条件分配空间;但可以通过下面四种方法来更改C编译器的缺省字节对齐方式:

方法1: 使用#pragma pack
     #pragma pack说明:
     1. pack提供数据声明级别的控制,对定义不起作用;
     2. 调用pack时不指定参数,将恢复C编译器的缺省字节对齐方式,即使用伪指令#pragma pack()将取消自定义字节对齐方式;
     3. 一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance可能会下降;
    
    #pragma pack语法详细说明:
    1. show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;
    2. push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;
    3. pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;
    4. identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
    5. n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

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

    -------------------------------------------
    例1:
    #pragma pack(2)
    struct TestA
{
public:
int   a; // 第一个成员,放在[0,3]偏移的位置。
char  b; // 第二个成员sizeof(char)=1,#pragma pack(2), 取小值也就是1,所以这个成员按1字节对齐,放在偏移[4]的位置。
short c; // 第三个成员sizeof(short)=2, #pragma pack(2),取小值也就是2,所以这个成员按2字节对齐,所以放在偏移[6,7]的位置。
char  d; // 第四个成员sizeof(short)=1, #pragma pack(2), 取小值也就是1,所以这个成员按1字节对齐,放在[8]的位置。
};
    #pragma pack()
    struct TestA中size最大的数据成员(4),#pragma pack(2), 取小值也就是2,所以sizeof(TestA)应当按照2来对齐,为10。

    -------------------------------------------
    例2:
    #pragma pack(4)
    struct TestB
{
public:
int   a; // 第一个成员,放在[0,3]偏移的位置。
char  b; // 第二个成员sizeof(char)=1,#pragma pack(4), 取小值也就是1,所以这个成员按1字节对齐,放在偏移[4]的位置。
short c; // 第三个成员sizeof(short)=2, #pragma pack(4),取小值也就是2,所以这个成员按2字节对齐,所以放在偏移[6,7]的位置。
char  d; // 第四个成员sizeof(short)=1, #pragma pack(4), 取小值也就是1,所以这个成员按1字节对齐,放在[8]的位置。
};
    #pragma pack()
    struct TestB中size最大的数据成员(4),#pragma pack(4),  取小值也就是4,所以sizeof(TestB)应当按照4来对齐,为12。

    -------------------------------------------
    例3:
    #pragma pack(8)
    struct s1
    {
        short a; // 第一个成员,放在[0, 1]偏移的位置。
        long  b; // 第二个成员sizeof(long)=4, #pragma pack(8), 取小值也就是4,所以这个成员按4字节对齐,放在偏移[4~7]的位置。
    };

    struct s2
    {
        char      c; // 第一个成员,放在[0]偏移的位置。
        struct s1 d; // 第二个成员为struct s1,其对齐方式是它的所有成员使用的对齐参数中最大的一个,即4。
                     // 所以第二个成员d按4字节对齐,由于sizeof(d)=8, 放在偏移[4~11]的位置。
        long long e; // 第三个成员sizeof(long long)=8, #pragma pack(8), 取小值也就是8,所以这个成员按8字节对齐,放在偏移[16~23]的位置。
    };
    #pragma pack()

    问:
    1. sizeof(struct s2) = ?
    2. s2的c后面空了几个字节接着是d?
    
    答案1:
        struct s1中size最大的数据成员(4),#pragma pack(8),取小值也就是4,所以sizeof(struct s1)应当按照4来对齐,为8。
        struct s2中size最大的数据成员(8),#pragma pack(8),取小值也就是8,所以sizeof(struct s2)应当按照8来对齐,为24。
    答案2:
        s2的c后面空了3个字节接着是d。
    
方法2: 使用__attribute((aligned (alignment)))
     aligned(alignment)属性作用于变量或结构成员,参数alignment表示最小的对齐字节数。
     例如:
     int x __attribute__ ((aligned (16))) = 0;
     使编译器为全局变量x分配空间在16字节边界。
     
     例如:创建一个以8字节为边界对齐的两个整数,可以写为:
     struct foo 
     { 
        int x[2] __attribute__ ((aligned (8))); 
     };

     前面两个例子中,指定参数alignment告诉编译器作用于变量或结构成员。
     但也可以不指定参数alignment,让编译器根据为编译的目标机采用最大最有益的方式对齐。
     例如:
          short array[3] __attribute__ ((aligned));
     一旦在aligned()属性中不指定参数,编译器会自动将变量或结构成员参数alignment设置为目标机上曾经使用的数据类型中最大的alignment,
     这样可以使copy操作效率更高。
     
     aligned属性只能用于增加alignment; 可以使用packed属性来减小alignment。
     
     注意:aligned属性应用效果受到链接器的限制。在许多系统中,链接器将变量对齐只能设置到某个最大值。
     假如使用的链接器将变量对齐最大只能设置为8字节,那么指定aligned(16)属性只能提供8字节的边界对齐。
     需要参考具体使用的链接器相关文档。

     举例:     
     struct A{
        char               a;
        int                b;
        unsigned short     c;
        long               d;
        unsigned long long e;
        char               f;
    };
    因为什么也没有跟,所以采用默认处理方式。其结果是与采用__attribute__((aligned(4)))相同。
    sizeof(struct A) = 4(a, 1-->4)+ 4 + 4(c, 2-->4) + 4 + 8 + 4(f, 1-->4) = 28。

     struct B{
        char               a;
        int                b;
        unsigned short     c;
        long               d;
        unsigned long long e;
        char               f;
    }__attribute__((aligned));
    在struct B中,aligned没有参数,表示“让编译器根据目标机制采用最大最有益的方式对齐"。
    当然,最有益应该是运行效率最高吧,呵呵。其结果是与采用__attribute__((aligned(8)))相同。
    sizeof(struct B) = 8(1+4+2 ,即a, b, c)+ 8(d, 4-->8) + 8 + 8(f, 1-->8) = 32。
    
     struct C{
        char               a;
        int                b;
        unsigned short     c;
        long               d;
        unsigned long long e;
        char               f;
    }__attribute__((aligned(1)));
    在struct C中,试图使用__attribute__((aligned(1)))来使用1个字节方式的对齐,不过并未如愿,仍然采用了默认4个字节的对齐方式。

方法3: 使用__attribute__ ((packed))取消结构在编译过程中的优化对齐
     __attribute__ ((packed))作用于结构成员,表示该成员与前一个结构成员之间没有空洞。
     举例:
     struct foo
     {
        char a;
        int x[2] __attribute__ ((packed));
     };
     这里packed属性作用于成员x,因而在结构成员a后没有空洞,而是立即紧跟着成员x。

     __attribute__ ((packed))作用于整个结构,等同于为结构中的每个成员指定__attribute__ ((packed)),也与结构前后利用#pragma pack(1)等效。
     举例:
     struct F{
        char               a;
        int                b;
        unsigned short     c;
        long               d;
        unsigned long long e;
        char               f;
    }__attribute__((packed));
    sizeof(struct F) = 1 + 4 + 2 + 4 + 8 + 1 = 20。
    
    注意:在使用了packed属性限定之后,GCC编译器将用字节存取命令(ARM中为LDRB或STRB指令)来访问该结构成员,
    而不是按照自然边界对齐方式来访问结构成员,可参见。
    
方法4: GCC编译选项中使用-fpack-struct[=n]
       如果没有指定n, 则去除所有结构中的空洞(注意这里会影响到所有的结构),即编译器不能在成员之间填充边界对齐的空字节。
       如果指定n, 则n表示maximum alignment (that is, objects with default alignment requirements larger than this will be output potentially unaligned at the next fitting location)。
       
       但通常不应当使用该选项,因为这会使访问结构成员的效率降低,代码量增大(通常会增加1/3左右,当Flash空间很有限时就要认真考虑了),
       而且使生成的代码与没有使用该编译选项的系统库不兼容。
           
4. 补充
   对于诸如char a[3];这种数组,它的对齐方式和分别写3个char是一样的,也就是说它还是按1个字节对齐。
   如果写为typedef char Array3[3];,则Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度。
   不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.
   
5. 参考资料
   [1] ARM体系结构下数据访问时的对齐问题.txt
   [2] GCC 4.3.2 Manual, [url]http://gcc.gnu.org/onlinedocs/[/url]
   [3] ARM嵌入式软件编程经验谈, 华清远见科技信息有限公司, [url]www.realview.com.cn/shoppic/iq-006/P22-23-ARM[/url]嵌入式软件编程经验谈.pdf 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tiny丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值