• C++字节对齐


    C++确实是一门很深奥很底层的语言…没想到就连字节对齐也有这么多门道。希望通过本文全面记录并帮助读者理解字节对齐。我觉得计算机知识的学习不仅要知其然,更要知其所以然,知道为什么要这样设计,而不是死记硬背,才能领悟前任这样设计的思想,未来真正运用的时候才能融会贯通

    什么是字节对齐

    计算机在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

    为什么需要字节对齐

    1. 提高访问效率:比如intel 32位cpu,每个总线周期都是从偶地址开始读取32位的内存数据,如果数据存放地址不是从偶数开始,则可能出现需要两个总线周期才能读取到想要的数据,而且还要对两次读取的结果进行拼凑才能得到完整的数据,因此需要在内存中存放数据时进行对齐。
    2. 平台原因(移植原因):不是所有硬件平台都能访问任意地址上的任意数据,有些平台可能智能从某些特定地址开始读取。

    对齐模数概念

    在讲解如何进行字节对齐前,先介绍对齐模数的概念

    • 许多实际的计算机系统对基本类型数据在内存中的存放位置有限制(也就是需要进行字节对齐),会要求这些数据的首地址值是某个值K(K是四的倍数)
    • 这个值K就是该数据类型的对齐模数
    • 当一种类型T1的对齐模数 / 另一种类型T2的对齐模数 > 1(且为整数),则称类型T1的对齐要求比T2严格,T2比T1宽松
    • 可以通过预编译命令#pragma pack(n)来指定对齐模数,n为2的整数幂
      • 实际的有效对齐模数是 指定对齐模数与类型自身对齐模数中的较小值,即 实际对齐模数 = min(指定对齐模数,这个数据成员的字节数)—这里要非常注意,下面的规则会频繁用到“实际对齐模数”这一概念

    ps:在我的64位OS上对齐模数是8,我猜测对齐模数可能与电脑的位数有关

    如何进行字节对齐

    结构体中字节对齐的规则

    先从结构体开始讲解,类的字节对齐规则实际上也是基于结构体的字节对齐规则。

    提示:以下例子的运行结果是在64位GCC编译器下得到的,在这一情况下每种基本类型占用的字节数位:char 1 short 2 int 4 float 4 double 8 指针 8

    不管是结构体还是类,内存对齐都遵循以下三个原则:

    1. 结构体每个数据成员的起始地址能够被该数据成员的实际对齐模数
    2. 结构体每个成员相对于起始地址的偏移能够被实际对齐模数整除,如果不能则在前一个成员后面补充字节(为了寻址)
    3. 结构体总体大小能够被最宽成员的实际对齐模数整除,如不能则在后面补充字节

    字节对齐的过程:

    1. 各成员变量在存放时会根据在结构中出现的顺序依次申请空间,同时按照上面的三个原则调整位置,空缺的字节会自动填充
    2. 为了确保结构的大小时结构的对齐模数的倍数,在为最后一个成员变量申请了空间之后,还会根据需要自动填充空缺的字节数

    下面给出一个实例:

    //我的电脑是64位机器,默认的对齐模数是8字节
    struct student_info {
        char name;//偏移量为0,满足对齐方式,name占用一个字节
        int age;//偏移量为1,不是 sizeof(int)的倍数,需要补足3个字节,age存放在偏移量为4的地址上,它自己占用4个字节
        int number;//偏移量为4,是sizeof(int)的倍数,不需要补足字节,number存放在偏移量为8的地址上,它自己占用4个字节
        char add;//偏移量为12,是sizeif(char)的倍数,不需要补足字节,add存放在偏移量为12的地址上,它自己占用1个字节
        double test;//偏移量为13,不是sizeof(double)的倍数,需要补足7个字节,补足后test存放在偏移量为16的地址上,它自己占用8个字节
        char tttt;//此时偏移量为24,是sizeof(char)的倍数,不需要补足字节,tttt存放在偏移量为24的地址上,它自己占用1个字节
    };//,所有成员变量都分配了空间,空间的总大小为25,不是实际对齐模数的倍数,需要填充字节,填充至32
    
    int main() {
        cout << sizeof(student_info) << endl;
        return 0;
    }
    //输出32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    假如上面的例子指定对齐模数为2

    #pragma pack(2)
    
    • 1

    答案会是22

    嵌套结构体的字节对齐规则

    (这部分参考了别人的博客)
    内嵌结构体的第一个成员编程在外结构体中的偏移量,是“MIN(指定对齐模数,内嵌结构体中最大数据类型)”的倍数。
    例子:

    struct TEST 
    {
        int a;  
        char b; 
        char c; 
        int d;
        char e;
        struct child {
            int f;
            long long g;  //8 bytes,long long 是chile结构体的最大数据类型
        }ch;
        char ci;
    };
    // sizeof(struct TEST)  = 40
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这个例子中,前面a,b,c,d,e的计算规则与普通结构体的计算规则相同,字符e的起始位置为12,占一个字节,然而由于child这个内嵌结构体的偏移量需要是min(最长数据成员所占字节 long long 8,对齐模数8) = 8的整数倍,因此需要在char e后进行填充,填充到16,然后再存,接下来的过程如下

    1. 存放int f:起始地址为 16,占4个字节
    2. 存放long long g:按照规则,起始地址需要是8的倍数,因此需要填充4个字节,该数据的实际其实地址为24,该数据占8个字节
    3. 存放char ci:起始地址为32,该数据占1个字节
    4. 最后,所有成员变量都分配了空间,空间的总大小为33,不是实际对齐模数的倍数,需要填充字节,填充至40,因此sizeof(struct TEST) = 40

    类中的字节对齐规则

    需要首先类中的字节对齐规则比较复杂,涉及虚函数、静态成员、虚继承、多继承、空类等情况。类本身是没有大小可言的,这里计算的sizeof(xxx)实际上是该类所对应对象的大小

    具体规则总结如下:

    1. 假如不是虚类,计算实际上与结构体的一致,不需要对函数、静态数据进行计算。类的静态数据成员被该类所有的对象所共享,并不属于具体哪个对象,静态数据成员定义在内存的全局区
    2. 假如是虚类,需要明确C++在实现虚类时其实隐藏了虚表指针(本质上就是指针类型)。注意基类有一个虚表指针。此外对于派生类,考虑到多继承的情况,有多少个继承就会有多少个虚表指针,在计算类大小的时候需要将虚表指针也纳入考虑
    3. 关于空类:因为C++中凡是一个独立的(非附属)对象都必须具有非零大小,不同的对象不能具有相同的地址,所以这里做了一个特殊处理:空类大小为1。假如是继承空类:这个空类对于派生类不占用空间。假如是拥有一个空类对象:空类对象占一个字节

    总结

    字节对齐看似简单,但是深究起来其实是对很多知识点的总结,包括

    • OS是怎么寻址的
    • C++虚函数的实现原理
      在平时学习的时候也要注意对知识点的融会贯通,学什么都是这样的。

    参考资料

    1. C++中的字节对齐_云飞扬_Dylan的博客-CSDN博客
    2. c++类的大小计算_rotation ㅤ   的博客-CSDN博客
    3. C++中的字节对齐
  • 相关阅读:
    Transformers x SwanLab:可视化NLP模型训练
    express学习6-express参数中get参数的获取
    【打卡】牛客网:BM38 在二叉树中找到两个节点的最近公共祖先
    【Git】Git基础命令操作速记
    6个顶级BI和数据可视化工具
    Markdown语法之数学公式【总结】
    奉劝那些刚参加工作的学弟学妹们:要想进大厂,这些核心技能是你必须要掌握的!完整学习路线!!(建议收藏)
    手撕红黑树的插入(C++)
    《Linux从练气到飞升》No.22 Linux 基础IO
    【软件工程之美 - 专栏笔记】“一问一答”第3期 | 18个软件开发常见问题解决策略
  • 原文地址:https://blog.csdn.net/Accepted_Lam/article/details/127990868