cpp 中的内存对齐
为什么需要内存对齐
需要字节对齐的根本原因在于 CPU 访问数据的效率问题。假如没有字节对齐,那么一个 double
类型的变量可能存储在 4-11 上(正常是 0-7),这样计算机在取这个数据时,会取两次,降低效率。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如 sparc 系统,如果取未对齐的数据会发生错误。
内存对齐是编译器对程序中数据的内存分配的一种优化方式。内存对齐的目的是为了提高内存系统的性能,减少内存访问的次数,从而提高程序的性能。
sizeof
运算符用于计算一个对象或类型所占的内存空间大小(以字节为单位)。在C++
中,为了提高访问内存的效率,编译器会对结构体中的成员进行内存对齐。- 提高访问内存的速度:现代处理器对内存的访问往往是按块进行的,如果数据按对齐方式存放,可以减少访问次数,提高效率。
- 兼容不同的硬件平台:不同的硬件平台对数据对齐的要求可能不同,内存对齐可以保证程序在不同平台上的兼容性。
struct 内存对齐代码示例
编译器优化
main.cpp
cpp
#include <iostream>
using namespace std;
struct X1
{
int i; //4 个字节
char c1; //1 个字节
char c2; //1 个字节
};
struct X2
{
char c1; //1 个字节
int i; //4 个字节
char c2; //1 个字节
};
struct X3
{
char c1; //1 个字节
char c2; //1 个字节
int i; //4 个字节
};
int main()
{
cout<<"long "<<sizeof(long)<<"\n";
cout<<"float "<<sizeof(float)<<"\n";
cout<<"int "<<sizeof(int)<<"\n";
cout<<"char "<<sizeof(char)<<"\n";
X1 x1;
X2 x2;
X3 x3;
cout<<"x1 的大小 "<<sizeof(x1)<<"\n";
cout<<"x2 的大小 "<<sizeof(x2)<<"\n";
cout<<"x3 的大小 "<<sizeof(x3)<<"\n";
return 0;
}
#include <iostream>
using namespace std;
struct X1
{
int i; //4 个字节
char c1; //1 个字节
char c2; //1 个字节
};
struct X2
{
char c1; //1 个字节
int i; //4 个字节
char c2; //1 个字节
};
struct X3
{
char c1; //1 个字节
char c2; //1 个字节
int i; //4 个字节
};
int main()
{
cout<<"long "<<sizeof(long)<<"\n";
cout<<"float "<<sizeof(float)<<"\n";
cout<<"int "<<sizeof(int)<<"\n";
cout<<"char "<<sizeof(char)<<"\n";
X1 x1;
X2 x2;
X3 x3;
cout<<"x1 的大小 "<<sizeof(x1)<<"\n";
cout<<"x2 的大小 "<<sizeof(x2)<<"\n";
cout<<"x3 的大小 "<<sizeof(x3)<<"\n";
return 0;
}
输出
long 8
float 4
int 4
char 1
x1 的大小 8
x2 的大小 12
x3 的大小 8
long 8
float 4
int 4
char 1
x1 的大小 8
x2 的大小 12
x3 的大小 8
内存对齐规则
- 由于在
x86
下,GCC
默认按 4 字节对齐,但是可以使用__attribute__
选项改变对齐规则,vs studio
上用#pragma pack (n)
方式改变 - 结构体成员的地址必须是其类型大小的整数倍。例如,int 型变量的地址必须是 4 的倍数。
- 基本数据类型:
long、float、int、char
在 32 位系统中通常分别占用4、4、4、1
字节。
输出结果分析
为什么会出现这样的输出? 是由于在 C++ 中,为了提高访问内存的效率,编译器会对结构体中的成员进行内存对齐。
- 内存对齐是导致结构体大小与成员类型大小之和不一致的主要原因。
- 编译器会根据不同的数据类型和系统设置进行内存对齐,以优化程序性能。
- 结构体
成员的排列顺序
会影响结构体的大小。
结构体 X1
、X3
x1
:int 占 4 字节,每个char
各占 1 字节。为了对齐,编译器会在char c2
后面再填充 2 个字节,使得int i
的地址是 4 的倍数。因此,整个结构体占字节。 x3
:与x1
类似,在char c2
后面填充 2 个字节,总共也是字节。
结构体 X2
x2
:char c1
占 1 字节,int i 占 4 字节。为了对齐,编译器会在int i
后面填充 3 个字节,使得char ch2
的地址是 1 的倍数。然后,为了使整个结构体的长度是 4 的倍数,又在char c2
后面再填充 3 个字节。因此,整个结构体占字节。
代码优化建议
合理安排成员顺序:将占用字节数大的成员放在前面,可以减少填充字节的数量,从而减小结构体的大小。
取消内存对齐
在某些情况下,可以使用#pragma pack
指令来控制结构体的对齐方式,但过度使用可能会降低程序性能。 示例:
C++
#include <iostream>
using namespace std;
struct X1
{
int i; //4个字节
char c1; //1个字节
char c2; //1个字节
};
#pragma pack(1)
struct X2
{
char c1; //1个字节
int i; //4个字节
char c2; //1个字节
};
#pragma pack()
struct X3
{
char c1; //1个字节
char c2; //1个字节
int i; //4个字节
};
int main()
{
cout<<"long "<<sizeof(long)<<"\n";
cout<<"float "<<sizeof(float)<<"\n";
cout<<"int "<<sizeof(int)<<"\n";
cout<<"char "<<sizeof(char)<<"\n";
X1 x1;
X2 x2;
X3 x3;
cout<<"x1 的大小 "<<sizeof(x1)<<"\n";
cout<<"x2 的大小 "<<sizeof(x2)<<"\n";
cout<<"x3 的大小 "<<sizeof(x3)<<"\n";
return 0;
}
#include <iostream>
using namespace std;
struct X1
{
int i; //4个字节
char c1; //1个字节
char c2; //1个字节
};
#pragma pack(1)
struct X2
{
char c1; //1个字节
int i; //4个字节
char c2; //1个字节
};
#pragma pack()
struct X3
{
char c1; //1个字节
char c2; //1个字节
int i; //4个字节
};
int main()
{
cout<<"long "<<sizeof(long)<<"\n";
cout<<"float "<<sizeof(float)<<"\n";
cout<<"int "<<sizeof(int)<<"\n";
cout<<"char "<<sizeof(char)<<"\n";
X1 x1;
X2 x2;
X3 x3;
cout<<"x1 的大小 "<<sizeof(x1)<<"\n";
cout<<"x2 的大小 "<<sizeof(x2)<<"\n";
cout<<"x3 的大小 "<<sizeof(x3)<<"\n";
return 0;
}
输出为
bash
long 8
float 4
int 4
char 1
x1 的大小 8
x2 的大小 6
x3 的大小 8
long 8
float 4
int 4
char 1
x1 的大小 8
x2 的大小 6
x3 的大小 8
上面的代码会强制将结构体 X2
中的成员按 1 字节对齐,从而减少结构体的大小。但是,需要注意的是,这种方式可能会降低程序性能,因此需要谨慎使用。
INFO
内存对齐的具体规则可能因编译器和系统而异。 过度关注内存对齐可能会导致代码可读性降低,因此在优化时需要权衡利弊。