内存对齐可能很多程序员接触不到,也许只在面试的偶尔会被问到过,但是也只是背背固定的公式,大概知道怎么计算,也能知道大致的原理,就是数据不对齐,取数次数要变多,但是只是理解到这种程度还不够,目前intel cpu不需要对齐也能访问,但是对于一些新的arm芯片,自研芯片等等当内存不对齐时候直接宕机给你看,自己在做hpc时,发现这个内存对齐问题还是一个比较严重的事情,这里好好的捋顺一下,避免自己以后遗忘。
综上可以看到,其实你的size如果小于default偏移位置就是你的整数倍,如果你的size大于default,其实无所谓分开取数了(因为你本来都超过一次取数的大小),所以偏移位置就是default的整数倍。提炼出来就是规则1。
下面这段代码可以看到,理论来说结构应该是6, 但是其实是8,所以结构体末尾padding出两个0,这就比较奇怪了?为什么?
其实是因为为了搞数组问题,Std num[10];问题来了,可以看到num[1]中的首地址是6,而6不是4的整数倍,但是如果padding两个位置的话,首地址就是8。
#include
#pragma pack(4)/*指定按4字节对齐*/
typedef struct Student
{
int i;
char c1;
char c2;
} Std;
std::cout<<sizeof(Std)<<std::endl;
std::cout<<offsetof(Std, i)<<std::endl;
std::cout<<offsetof(Std, c1)<<std::endl;
std::cout<<offsetof(Std, c2)<<std::endl;
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
结构嵌套内存对齐问题:
class Inner
{
char a;
char b;
char c;
}
class Outter
{
char d;
Inner e;
}
sizeof(Inner) == 3;
sizeof(Outter) == 4;
根据上面的两个原则,这里的默认值是4字节。Inner的内存是3毋容置疑,但是Outter就有点奇怪了?理论来说e的偏移应该是min(4,3)的整数倍,那么e的位置应该是3,4,5,其中0号位置放d, 1,2位置空着,接着就是整体大小是min(4,3)的整数倍,所以Outter的size应该是6的,现在为啥是4呢?
这个问题原因是首先偏移位置算错了,不能把这个Inner当成一个3字节的内存,应该是min(4, max(Inner))的整数倍,其次Outter的整体size也是min(4,max(Inner))的整数倍。
综上可以看到,其实你struct的结构的大小就是需要去padding的。提炼出来就是规则2。此外要清楚内存对齐是编译器做的事情,编译器并不清楚你运行的机器到底是按什么方式取地址的,所以一般只是提供一个大部分机器都使用的值。
#pragma pack(4)//pack里面可以设置为(1,2,4,8,16)
#include
struct
{
int i;
char c1;
char c2;
}x1;
struct{
char c1;
int i;
char c2;
}x2;
struct{
char c1;
char c2;
int i;
}x3;
//如果想单独对某一个类型采用对齐方式,可以如下写法
struct Student
{
char c1;
int c2;
double c3;
}__attribute__((aligned(8)));
int main()
{
printf("%d\n",sizeof(x1)); // 输出8
printf("%d\n",sizeof(x2)); // 输出12
printf("%d\n",sizeof(x3)); // 输出8
return 0;
}
链接1
链接2
aligned和pack的意思完全不相同,这个意思是test的起始位置要是16的倍数,如果是结构的话,size也要是16的倍数
class test
{
int a;
int b;
};__attribute__((aligned(16)));
align的使用场景是:1. 变量 2. 类型
不会
改变变量的大小。例子:int test _ attribute_((aligned(16)));该变量a的内存起始地址为16的倍数。可能
会改变结构体的大小利用alignof可以查看当下的数据类型的pack值是多少。
利用alignas来指定对齐大小,
### demo_1
struct alignas(2) A
{
int a;
char b;
};
alignof(A)==4 //max(alignas, max(A)=int)
sizeof(A)==8 //min(int, alignof),所以必须是4的整数倍
### demo_2
struct alignas(2) A
{
int a;
double b;
};
alignof(A)==8 //max(alignas, max(A)=double)
sizeof(A)==16 //min(double, alignof)所以必须是8的整数倍
### demo_3
struct alignas(2) A
{
double b;
int a;
};
alignof(A)==8 //max(alignas, max(A)=double)
sizeof(A)==16 //min(double, alignof)所以必须是8的整数倍
### demo_4
#pragma pack(4)
struct alignas(2) A
{
double b;
int a;
};
#pragma pack(4)
alignof(A)==4 //不知道为啥
sizeof(A)==12 //min(double, alignof)所以必须是4的整数倍
### demo_5
#pragma pack(4)
struct alignas(16) A
{
double b;
int a;
};
#pragma pack(16)
alignof(A)==16 //不知道为啥
sizeof(A)==16 //min(double, alignof)所以必须是16的整数倍
/*
template< std::size_t Len, std::size_t Align = default-alignment >struct::type aligned_storage;
相当于一个内建的POD类型他的大小是Size他的对齐方式是Align
*/
typename std::aligned_storage<sizeof(T), __alignof(T)>::type data[N];
其实对一个结构或者对象,之所以给一个对齐属性是因为:编译器可以根据这个值选用不用的访存指令
#pragma pack(push, 8)
struct alignas(16) test
{
int a[10];
};
#pragma pack(pop)
int main()
{
test v;
std::cout << "alignof= " << alignof(test)<(&v);
std::cout << ptr <
上面的代码,一旦test的属性被定位16后,那么即使a[N]的N再大,那么编译器也只会调用ldg.16的指令去读取这个结构。所以如果机器支持ldg.N的向量化处理,那么这里最好设置为N,这样可以减少指令次数,提高性能。
#pragma pack(push, 1)
struct alignas(2) test
{
int a[10];
};
#pragma pack(pop)
test v;
std::cout << "alignof= " << alignof(test::a)<
alignof= 1
alignof= 1
sizeof= 40
alignof= 1
alignof= 2
sizeof= 40
#pragma pack(push, 1)
struct alignas(16) test
{
int a[10];
};
#pragma pack(pop)
//cout
alignof= 1
alignof= 16
sizeof= 48
下面是一些可以魔改的demo,可以用来验证自己的想法是不是正确:
#include
#pragma pack(push, 2)
struct alignas(16) test
{
char a;
double b;
};
#pragma pack(pop)
int main()
{
test v;
std::cout << "alignof= " << alignof(test::b)<(&v);
long long ptr_b = reinterpret_cast(&(v.b));
std::cout << ptr <
利用这个可以测测cpu上的向量化操作,gcc -march=native, 参考代码
#include
#include
int main() {
__attribute__ ((aligned (32))) double input1[3] = {1, 1, 1};
__attribute__ ((aligned (32))) double input2[3] = {1, 2, 3};
__attribute__ ((aligned (32))) double result[5];
double* pt = &input1[2];
pt = pt+1;
*pt= 1.38392e39;
std::cout << "address of input1: " << reinterpret_cast(input1) << std::endl;
std::cout << "address of input2: " << reinterpret_cast(input2) << std::endl;
__m256d a = _mm256_load_pd(input1);
__m256d b = _mm256_load_pd(input2);
__m256d c = _mm256_add_pd(a, b);
_mm256_store_pd(result, c);
std::cout << result[0] << " " << result[1] << " " << result[2] << " " << result[3] <<" "<