请稍侯

C语言字节对齐及结构体对齐详解

04 December 2020

C语言字节对齐及结构体对齐详解

为什么需要字节对齐

对齐与数据在内存中的位置相关,若一个变量的内存地址正好位于它长度的整数倍,那么就称做自然对齐。如在32位CPU下,假设一个整型变量的地址为0x00000004,那它就是自然对齐的。

字节对齐的作用不仅有利于CPU快速访问,同时合理的利用字节对齐还可以有效地节省内存。
如果上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。

一般情况下,各类型变量的地址要求是该类型字节大小的整数倍,如:

char 1-字节对齐
short 2-字节对齐
int 4-字节对齐
long 4-字节对齐
float 4-字节对齐
double 8-字节对齐(部分机器是4字节对齐)
long long 8-字节对齐
long double 8-字节对齐(部分机器是4字节对齐)
指针 4-字节对齐(32位系统) 8-字节对齐(64位系统)
struct 4-字节对齐(32位系统) 8-字节对齐(64位系统)

以Stuct为例:

//定义1个空的结构体
struct Obj {
};
struct Obj i, j;
    
printf(" %p %lu \n %p %lu \n", &i, sizeof(i), &j, sizeof(j));

//输出:0x16fd0e1f0 0 0x16fd0e1e8 0

会发现即使是两个空的struct变量,也按照机器字长偏移了8个字节(我们的机器是64位系统)。
如果修改这个结构Obj的定义为:

struct Obj {
    char i; //1字节
    //---填充1个字节,确保j的起始地址为2的倍数
    short j;  //2字节
    //---填充4个字节,确保k的起始地址为8的倍数
    double k; //8字节
};

//输出:0x16fd621e8 16 0x16fd621d8 16

如果再次修改定义为:

struct Obj {
    char i; //1字节
    //---填充7个字节,确保k的起始地址为8的倍数
    double k; //8字节
    //---不需要填充,j的起始地址正好为2的倍数
    short j;  //2字节
    //---填充6个字节,确保Obj的大小为8的倍数
};

//输出:0x16fdaa1e0 24 0x16fdaa1c8 24

会发现Obj的大小突然变大到了24,这是因为j后还要填充6个字节确保整个Obj的结构大小。

所以,我们应该尽量按照变量的大小从小到大进行声明,以节省内存

设定对齐大小

更改C编译器的缺省字节对齐方式,可以使用pragma pack伪指令来改变。如以下例子: 在网络通信中,如果我们需要直接发送struct,收发方的对齐方式肯定会不一样,这时我们可以严格控制字节对齐。比如:

#pragma pack(push)
#pragma pack(1) //1字节对齐
struct Obj {
    char i;
    double k;
    short j;
};
#pragma pack(pop)

//输出:0x16fde61e8 11 0x16fde61d8 11

我们发现缩小对齐字节为1时,Obj的大小变为实际变量的大小之和:11字节。

另外,还有一种方式是使用GNU的_attribute__选项来设置。

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

struct Obj2 {
    char i;
    double k;
    short j;
} __attribute__ ((aligned(1)));

struct Obj3 {
    char i;
    double k;
    short j;
} __attribute__ ((packed));

struct Obj obj;
struct Obj2 obj2;
struct Obj3 obj3;
printf("== sizeof Obj:%lu \t Obj2:%lu \t 
        Obj3:%lu",sizeof(obj),sizeof(obj2),sizeof(obj3));

//输出:== sizeof Obj:24 	 Obj2:11 	 Obj3:11