关于内存对齐的一些总结

3/3/2017来源:C/C++教程人气:1352

关于内存对齐的一些总结 在介绍内存对齐之前,我们需要了解几个关于内存方面的知识      1)#PRagma pack(n)    用途:设定变量以n字节对齐    程序编译器对结构的存储的特殊处理能提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。    编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。 2)基本变量在不同系统下的字节数       在X86,32位系统下基于Microsoft、Borland和GNU的编译器,#pragma pack(4),即4字对齐 有如下数据对齐规则:        a、一个char(占用1-byte)变量以1-byte对齐。        b、一个short(占用2-byte)变量以2-byte对齐。        c、一个int(占用4-byte)变量以4-byte对齐。        d、一个long(占用4-byte)变量以4-byte对齐。        e、一个float(占用4-byte)变量以4-byte对齐。        f、一个double(占用8-byte)变量以8-byte对齐。        g、一个long double(占用12-byte)变量以4-byte对齐。        h、任何pointer(占用4-byte)变量以4-byte对齐。             而在64位系统下,#pragma pack(8),即8字对齐 与上面规则对比有如下不同:       a、一个long(占用8-byte)变量以8-byte对齐。       b、一个double(占用8-byte)变量以8-byte对齐       c、一个long double(占用16-byte)变量以16-byte对齐。       d、任何pointer(占用8-byte)变量以8-byte对齐。 3)在结构体中成员数据必须满足以下规则     结构体数据对齐,是指结构体内的各个数据对齐。在结构体中的第一个成员的首地址等于整个结构体的变量的首地址,而后的成员的地址随着它声明的顺序和实际占用的字节数递增。为了总的结构体大小对齐,会在结构体中插入一些没有实际意思的字符来填充(padding)结构体。      a、结构体中的第一个成员的首地址即结构体变量的首地址。       b、结构体中的每一个成员的首地址相对于结构体的首地址的偏移量(offset)是该成员数据类型大小(sizeof(该成员))的整数倍。      c、在数据成员完成各自对齐之后,结构(或联合)体本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,且为它的整数倍。 4)以下是我在Ubuntu  x64位下做的demo(因为只有64位,32位的就不验证了@_@)
#include <stdio.h>

struct stu1{
    char a;
    int b;
    short c;
}stu1;
struct stu2{
    short c;
    char a;
    int b;
}stu2;
struct stu3{
    char a;
    short c;
    int b;
}stu3;
struct stu4{
    char a;
    int b;
    short c;
    double d;
    char e;
}stu4;
int main(int argc, char *argv[])
{
    printf("stu1.a:%p   stu1.b:%p     stu1.c:%p    sizeof(stu1):%d\n", &stu1.a, &stu1.b, &(stu1.c), sizeof(stu1));
    printf("stu2.c:%p   stu2.a:%p     stu2.b:%p    sizeof(stu2):%d\n", &stu2.c, &stu2.a, &(stu2.b), sizeof(stu2));
    printf("stu3.a:%p   stu3.c:%p     stu3.b:%p    sizeof(stu3):%d\n", &stu3.a, &stu3.c, &(stu3.b), sizeof(stu3));
    printf("stu4.a:%p   stu4.b:%p     stu4.c:%p    stu4.d:%p    stu4.e:%p    sizeof(stu4):%d\n", &stu4.a, &stu4.b, &stu4.c, &stu4.d, &stu4.e, sizeof(stu4));
    return 0;
}


运行结果如下





分析stu1:stu1结构体可以看到,a是一个char型,为一个字节,接下来为int b,为整形,四个字节,这时候离结构体首地址(即a的地址)的偏移为4规则b),所以a必须再填充三个字节(从打印的地址也可以看出,即0x6010a9、0x6010aa、0x6010ab),然后short
 c2个字节,而且距离首地址偏移为6能整除2(short) 所以加起来为4+4+2 = 10字节。这时候肯定很多人疑问了,这不10字节么?怎么打印的是12呢?那么现在我们的规则c起作用了(即结构体本身的对齐),在64位系统下,#pragma
 pack(8)  为8stu1结构体中最大的类型为int4个字节,所以我们需要在short
 c后面填充2个字节(从打印地址可以看出,c的首地址为0x6010b0、那么填充的地址应该为0x6010b2、0x6010b3),所以总共的字节数为4+4+2+2
 = 12  (字节)。
中间两个就不分析了,按着规则去一步一步匹配就行,而且地址我都打印出来了应该好判断,接着分析下stu4。
分析stu4:从stu4结构体可以看到(和结构体stu1相似,我特意添加了一个double和char来区分下以及更加好理解规则c),char a,int b,short c,我就不再具体分析,可以看下分析stu1,算到这里我们知道是4+4+2
 = 10 字节,接下来是double d,为8字节,到这里我们可以知道a+b+c+d = 4+4+2+8 = 18(这时候注意了,根据规则b,我们知道18是不能整除8的,所以必须填充4字节,为18+6 = 24),所以这时候我们不妨看看打印的地址是否和我们想的一样(short
 c的首地址为0x601088,占2字节,0x601088~0x601089,从图中我们可以看到d的首地址为0x601090,所以就证实了我们说的需要填充6个字节,即0x60108a~0x60108f),最后加上char e ,1个字节,为25字节,这时候我想很多人应该知道了,根据我们的规则c,在64位系统下,#pragma
 pack(8)  为8stu4结构体中最大的类型为double8个字节,所以我们需要在char
 e后面在填充7个字节,即0x601099~0x6010af,所以总共的字节数为1+3(填充)+4+2+6(填充)+8+1+7(填充) = 32(字节)。


由于上周看到网上很多对内存对齐都讲的很模糊,所以决定自己玩一遍,毕竟身为程序猿各种玩就是@_@,周末总结下希望能帮助到别人!!!该睡觉了,还要上班明天......