内存对齐问题

背景

内存对齐可能很多程序员接触不到,也许只在面试的偶尔会被问到过,但是也只是背背固定的公式,大概知道怎么计算,也能知道大致的原理,就是数据不对齐,取数次数要变多,但是只是理解到这种程度还不够,目前intel cpu不需要对齐也能访问,但是对于一些新的arm芯片,自研芯片等等当内存不对齐时候直接宕机给你看,自己在做hpc时,发现这个内存对齐问题还是一个比较严重的事情,这里好好的捋顺一下,避免自己以后遗忘。

规则

  • 结构体中大小为 size 的字段,他的结构内偏移 offset 需要是其min(default, size)的整数倍 //default是#pragma pack(default)
  • 结构体整体的大小是min(default,max(struct))的整数倍

例子1

  • 情况1:
    假如现在有一个32位的cpu,default为4,现在有一个数据在2,3,4,5四个位置(每个位置一个字节),那么需要两次读取,第一次就是读取0123到寄存器中,第二次就是读取4567到寄存器中,然后拼接出来2345这个数据。内存对齐问题_第1张图片
    所以当size正好等于default(4)的时候,假如这个偏移位置是4的时候,就可以避免分两次来提取数字。
  • 情况2:
    当size大于default的时候,就无法避免分两次(也许更多次)来提取数字,所以偏移位置是default的时候就ok了。
  • 情况3:
    当size小于default的时候,现在有一个数据在2,3四个位置(每个位置一个字节),那么需要一次读取,第一次就是读取0123到寄存器中,然后左移两个字节
    内存对齐问题_第2张图片
    这个问题发现,如果一个数据结构的size小于一个读取size的时候,理论上来说放在1,2和放在2,3没啥区别,这个就涉及到数据传输的问题,假如这个数据传输到一个CPU只能读取2个字节的CPU,这样在1,2这样的话就必须新的CPU就只能读取两次,而23还是一次,所以大家就定一个规则你的偏移位置放在你自己size的整数倍。

总结

综上可以看到,其实你的size如果小于default偏移位置就是你的整数倍,如果你的size大于default,其实无所谓分开取数了(因为你本来都超过一次取数的大小),所以偏移位置就是default的整数倍。提炼出来就是规则1。

例子2

下面这段代码可以看到,理论来说结构应该是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 () /*取消指定对齐,恢复缺省对齐*/

例子3

结构嵌套内存对齐问题:

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. 类型

  • 当aligned作用于变量时,其作用是告诉编译器为变量分配内存的时候,要分配在指定对齐的内存上,作用于变量之上不会改变变量的大小。例子:int test _ attribute_((aligned(16)));该变量a的内存起始地址为16的倍数。
  • 当aligned作用于类型时,其作用是告诉编译器该类型声明的所有变量都要分配在指定对齐的内存上。当该属性作用于结构体声明时可能会改变结构体的大小

alignof

利用alignof可以查看当下的数据类型的pack值是多少。

alignas

利用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的整数倍

aligned_storage

/*
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)< 
   
  • 然后加上alignas(2),其实这时候已经不影响内存struct内存的变量的对齐方式了,只是改变test的对齐方式,大小取N= max(alignas, test_align),所以sizeof也会是N的整数倍
  • alignof= 1
    alignof= 2
    sizeof= 40
    
    1. 测试
    #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)<(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] <<" "<

    你可能感兴趣的:(C++,基础,CUDA编程,c++,c语言)