C++——内存对齐与#pragma pack的作用

452 阅读4分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路。

image.png

(欢迎大家关注我的微信公众号——控制工程研习,上面会分享很多我学习过程中总结的笔记。)

1. 什么是内存对齐

    在 C/C++ 中,结构体/类是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。编译器为每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。

     如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。 如果在 32 位的机器下,一个int类型的地址为0x00000004,那么它就是自然对齐的。同理,short 类型的地址为0x00000002,那么它就是自然对齐的。char 类型就比较 "随意" 了,因为它本身长度就是 1 个字节。

    各种数据类型的自然对齐:

char 偏移量为sizeof(char)   即 1 的倍数
short 偏移量为sizeof(short)  即 2 的倍数
int 偏移量为sizeof(int)    即 4 的倍数
float 偏移量为sizeof(float)  即 4 的倍数
double 偏移量为sizeof(double) 即 8 的倍数

在设置结构体或类时,如果不考虑内存对齐问题,会浪费一些空间,测试一:

struct asd1{    
    char a;    
    int b;    
    short c;
};//12bytes 
 
 struct asd2{    
     char a;    
     short b;    
     int c;
};//8bytes

上面两个结构体拥有相同的数据成员 char、short 和 int,但由于各个成员按照它们被声明的顺序在内存中顺序存储,所以不同的声明顺序导致了结构体所占空间的不同。具体情况如下图:

图片图片

    看到上面的第二张图,为什么 short 不是紧挨着 char 呢?其实这个原因在上面已经给出了答案——自然对齐。为此,我们可以创建结构体验证自然对齐的规则。测试很简单,在原本 short 类型变量前后添加 char 类型,看结果是怎样的。测试二:

struct asd3{    
    char a;    
    char b;    
    short c;    
    int d;
};//8bytes 

struct asd4{    
    char a;    
    short b;    
    char c;
    int d;};//12bytes

图片图片

    当数据成员中有 double 和 long (8字节) 时,情况又会有一点变化。还是以上面的结构体 asd1 和 asd2 为基础,都添加 double 型数据成员。来看看结果是什么,测试三:

struct asd1{  
    char a;  
    int b;  
    short c;  
    double d;
};//24 bytes 

struct asd2{  
    char a;  
    short b;  
    int c;  
    double d;
};//16 bytes

只添加了一个 double,但 struct asd1 的大小从 12 变到了 24。而 struct asd2 的大小从 8 变到了 16。不需要迷惑,因为这和 double 的自然对齐有关(需要注意)。原本的 asd1 占 12 个字节大小,但是 double 对齐需要是 8 的倍数,所以在 short 后面又填充了 4 个字节。此时,asd1 的占 16 个字节,再加上 double 的 8 个字节就成了 24 个字节。而 asd2 没有这个问题,它原本占 8 个字节。因为正好能对齐,所以添加double 后占 16 个字节。具体情况如下图所示:

图片图片


2. #pragma pack指令指定对齐值

****2.1 作用

    在缺省情况下,C 编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:

使用伪指令 #pragma pack (n),C 编译器将按照 n 个字节对齐。

使用伪指令 #pragma pack (),取消自定义字节对齐方式。

    测试四:

#pragma pack(4)
struct asd5{    
    char a;    
    int b;    
    short c;    
    float d;    
    char e;
};//20 bytes

#pragma pack() 
#pragma pack(1)
struct asd6{    
    char a;    
    int b;    
    short c;    
    float d;    
    char e;
};//12 bytes
#pragma pack()

使用 #pragma pack (value)指令将结构体按相应的值进行对齐。两个结构体包含同样的成员,但是却相差 8 个字节。难道我们只需要通过简单的指令就能完成内存对齐的工作吗?其实不是的。上面的对齐结果如下:

图片

    以 32 位机器为例,CPU 取的字长是 32 位。所以上面的对齐结果会这样带来的问题是:访问未对齐的内存,处理器需要作两次内存访问。如果我要获取 int 和 float 的数据,处理器需要访问两次内存,一次获取 "前一部分" 的值,一次获取 "后一部分" 的值。这样做虽然减少了空间,但是增加访问时间的消耗。

    其实最理想的对齐结果应该是下面这种情况,也就是说创建结构体时,变量的存储顺序也是需要考虑的。

图片