内存地址对齐
- 调试
- 2024-09-03
- 345热度
- 0评论
内存地址对齐是在内存中的数据(具体为变量的地址、内存块的地址)按照指定地址长度对齐,包含了基本的变量数据对齐和结构体数据对齐。
为什么需要内存对齐?
可以提高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 属性声明。这样做的好处是,既避免了结构体内因地址对齐产生的内存空洞,又指定了整个结构体的对齐方式。