Loading

C++内存对齐

0x1 什么是内存对齐,为什么需要它?

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,4字节,8字节,16字节甚至32字节为单位来存取内存,这些存取单位称为内存存取粒度

现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作。

0x2 内存对齐规则

  • 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。

  • 结构体的总大小为有效对齐值的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

    C++11后可以使用alignof获取一个结构体类型的有效对齐值

#include <iostream>

struct Info {
    uint8_t x1;
    uint32_t x2;
    uint64_t x3;
};

int main(int argc, char *argv[]) {
    std::cout << alignof(Info) << std::endl;// 输出8
    std::cout << sizeof(Info) << std::endl;// 输出16
    return 0;
}

/*
这里因为Info的有效对齐值为8,那么x2的对齐值是min(sizeof(uint32_t), 8) = 4,即其offset为4的整数倍,
x3的对齐值为8,其offset为8的整数倍。
所有Info的大小为 4 + 4 + 8 = 16
*/

如果调换x2和x3的顺序,看看会发生什么...

#include <iostream>

struct Info {
    uint8_t x1;
    uint64_t x3;
    uint32_t x2;
};

int main(int argc, char *argv[]) {
    std::cout << alignof(Info) << std::endl;// 输出8
    std::cout << sizeof(Info) << std::endl;// 输出24
    return 0;
}

/*
这里因为Info的有效对齐值为8,x3的offset需要为8的整数倍,会导致Info对象的字节数变大
所有Info的大小为 8 + 8 + 8 = 24
*/

0x3 改变对齐规则

  • alignas加在结构体前,如果小于有效对齐值会被忽略(只能加大对齐值)

    struct alignas(8) S {};
    struct alignas(1) U { S s; }; // 错误:如果没有 alignas(1) 那么 U 的对齐将会是 8
    
  • 使用#pragma pack

#pragma pack (n)         // 作用:C编译器将按照n个字节对齐。
#pragma pack ()          // 作用:取消自定义字节对齐方式。

#pragma pack (push,n)   // 作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为n个字节对齐
#pragma pack(pop)        // 作用:恢复对齐状态

举个例子

#pragma pack (1)   // 设置对齐方式

struct A
{
    int a;
    char b;
    short c;
    double d;
};

#pragma pack ()   // 取消自定义对齐方式

#pragma pack (push, 1)  // 设置对齐方式

struct B
{
    int a;
    char b;
    short c;
    double d;
};

#pragma pack (pop)   // 恢复对齐方式

/* 
这里sizeof(A) = 15,sizeof(B) = 15,因为它们的对齐值都为1
*/
posted @ 2023-04-09 15:37  想睡觉的人  阅读(14)  评论(0编辑  收藏  举报