内存地址对齐

内存地址对齐是在内存中的数据(具体为变量的地址、内存块的地址)按照指定地址长度对齐,包含了基本的变量数据对齐和结构体数据对齐。

为什么需要内存对齐?
可以提高CPU和内存交互的效率,比如一个32位的系统,CPU读取内存,硬件设计上只支持4字节或4字节的倍数对齐进行地址访问,CPU在每次访问内存时,一个周期可以访问4字节,如果要访问的数据是4字节对齐的地址,CPU一次就可以把数据访问完毕;如果访问的数据不是4字节对齐,cpu就需要分两次才能把4字节数据访问完成。

什么时候完成的内存对齐?

为了与具体的arch设计提高运行效率,编译器会自动完成内存对齐操作,在编译程序时,对应基本的数据类型,如int,char,short,float等,会按照其数据类的大小进行地址对齐,这样对齐方式分配的存储地址,CPU一次就可以访问完毕。这样即使会造成内存的空洞,浪费一些内存单位,但是对于硬件设计和运行效率可以极大的简化和提升。除了了基本的数据类型外,包括一些复合数据类型如结构体也要满足对齐要求。

数据类型对齐

32位系统,编译对齐规则

char: 1字节对齐
short:2字节对齐
init:4字节对齐
float:4字节对齐
double:8字节对齐
指针:4字节对齐

64为系统,编译对齐规则
指针:8字节对齐

结构体类型对齐

结构体数据内存对齐,具体是结构体内的各个数据对齐。结构体作为一种复合数据类型,编译器在分配存储空间时,不仅要考虑结构体内各个基本成员的地址对齐,还要考虑结构体整体的对齐。

  • 成员变量对齐:按照各自成员变量类型对齐,如32位系统 int 为4字节对齐
  • 结构体整体对齐:成员变量最大对齐字节或其整数倍对齐,在尾部补齐。如最大成员对齐是4,那么就需要是4的整数倍。

结构体的大小为什么需要按照最大成员变量填充对齐?

当数据按照一定的对齐规则进行排列时,CPU可以更高效地访问这些数据。这是因为CPU在读取内存数据时,通常是按照固定的大小块来读取的。例如,在64位系统中,CPU从内存的0-7位置开始读取,然后是8-15,16-23以此类推。如果结构体的成员没有按照最大成员变量的大小进行对齐,那么在连续的结构体数据(如结构体数组)中,变量的位置可能不再合理,导致读取效率下降。例如,如果一个int64类型的变量原本应该从0-7位置开始连续读取,但如果前面的成员没有对齐,下一个结构体的起始位置就可能不对,导致需要多次读取和拼接数据,进而影响性能‌1。

示例1

struct data {
     char a;    //1字节对齐, 实际占用4字节,由于后面的c变量
     int c ;    //4字节对齐, 因此a后面要填补3字节数据,相当于a占用了4字节
     short b ;  //2字节对齐,但是后面要补2字节,因为3个成员变量占了4+4+2=10,
                //成员中最大的变量长度是4字节对齐,因此整个数据结构要是4个整数倍
                //因此后面要补2字节。
};

sizeof(struct data) = 12

示例2

struct data{
     char a;   //1字节对齐,由于后面的short是2字节对齐,因此后面补了1字节
     short b ; //2字节对齐
     int c ;   //4字节对齐
};

sizeof(struct data) = 8

从上面的示例可知,结构体中的变量排列会影响实际的空间大小,因此在定义结构体时,尽量的从小变量到大变量排列,这样节省内存。

aligned与packed

GNU C通过 atttribute 来声明 aligned 和 packed 属性,指定一个变量或类型的对齐方式。这两个属性用来告诉编译器:在给变量分配存储空间时,要按指定的地址对齐方式给变量分配地址。

aligned

如果你想定义一个变量,在内存中以8字节地址对齐,就可以这样定义。

int a __attribute__((aligned(8));

通过 aligned 属性,我们可以直接显式指定变量 a 在内存中的地址对齐方式。aligned 有一个参数,表示要按几字节对齐,使用时要注意地址对齐的字节数必须是2的幂次方,否则编译就会出错。

struct data{
     char a;   //4
     short b __attribute__((aligned(4)));  //4
     int c ;  //4
};

sizeof(struct data) = 12

上述的示例表示struct data成员变量中的short b要按4字节强行对齐。

struct data{
     char a; //4
     short b; //4
     int c ; //8
 }__attribute__((aligned(16)));

sizeof(struct data) = 16

可以显示指定了整个结构体的对齐方式,如上示例显式指定结构体整体以16字节对齐,所以编译器就会在这个结构体的末尾填充8个字节以满足16字节对齐的要求,导致结构体的总长度变为16字节。

packed

aligned属性一般用来增大变量的地址对齐,元素之间因为地址对齐会造成一定的内存空洞。而 packed属性则与之相反,用来减少地址对齐,用来指定变量或类型使用最可能小的地址对齐方式。

struct data{
     char a;  //1
     short b __attribute__((packed)); //2
     int c __attribute__((packed)); //4
 };

sizeof(strut data) =7

使用了packed属性,告诉编译器使用最小的对齐方式。

aligned与packed一起使用

struct data{
     char a; //1
     short b ; //2
     int c ; //5
}__attribute__((packed,aligned(8)));

sizeof(struct data) = 8

aligned 和 packed 一起使用,即对一个变量或类型同时使用 aligned 和 packed 属性声明。这样做的好处是,既避免了结构体内因地址对齐产生的内存空洞,又指定了整个结构体的对齐方式。