• C/C++ 结构体(struct)对齐问题


    事情起因是这样的

    以前只记得结构体对齐,是对齐最长的那个成员,但现在发现并不是这样,看以下两个示例(64位 g++ 9.3.0 编译)

    示例一

    class B
    {
        public:
            char b;                 // 8
            virtual void fun() {};  // 8
            static int c;           // 0
            static int d;           // 0
            static int f;           // 0
    };
    
    cout<<sizeof(B)<<endl; // 16
    

    值得一提的是静态成员不占空间
    这里虚指针 = 8B,char b = 1B。最宽成员是虚指针,所以char b对齐为 8B,所以总 16B,可能一开始大家都是这么理解的(但实际上不对,不能这么说,后文会指正)

    示例二

    class A
    {
        public:
            char a; // 4
            int b;  // 4
    };
    class C
    {
        A a;        // 8
        char c;     // 4???
    };
    
    cout<<sizeof(C)<<endl; // 12
    

    类 C 中char c成员实际结果是 4B。
    如果按原来的理解,类 A = 8B,类 C 继承后最宽成员是 A 类,那char c不应该对齐为 8B 吗?

    实际情况

    参考 LeetCode某讨论

    假定是gcc,用默认的对齐系数 4 (编译器决定)
    对齐值是对齐系数和结构体各成员长度最大值的较小值
    对齐要求各成员起始偏移量是对齐值和它的长度的较小值的倍数

    一句话概括就是

    • 对齐值 = min{对齐系数,最长成员}
    • [补]对齐系数用#pragma pack(n)指定,其中 n = 1, 2, 4, 8..等二次幂;默认情况下,貌似 32-bit 机是 4,64-bit 机是 8
    • 起始偏移 = min{对齐值,成员自身长度},再取整数倍
    • 最后整个结构体的大小要补足为对齐值的倍数

    举个例子(还是拿上面这个讨论作例子)

    参考 LeetCode某讨论

    struct student {
        int m_id;        // 4
        char m_name[10]; // 10
        bool m_sex;      // 1
        int m_age;       // 4
    };
    

    这里先给出分析结构体长度的一般方法:

    • 得出对齐值
    • 分析各成员的偏移地址
    • 再分析结构体占用空间大小

    第一步,得出对齐值:

    • 对齐值 = min{对齐系数(4), 最长成员(10)} = 4(假设是 32 位机)

    第二步,分析各成员偏移地址:

    • 成员m_id自身长度 4,对齐值 4,因此 min = 4。作为第一个成员,偏移地址为 0,而 0 是 4 的倍数,因此偏移 0,占用空间 0-3
    • 成员m_name[]自身长度 10,对齐值 4,因此 min = 4。续上一个成员结尾偏移地址为 4,而 4 是 4 的倍数,因此偏移 0,占用空间 4-13
    • 成员m_sex自身长度 1,对齐值 4,因此 min = 1。续上一个成员结尾偏移地址为 14,而 14 是 1 的倍数,因此偏移 14,占用空间 14
    • 成员m_age自身长度 4,对齐值 4,因此 min = 4。续上一个成员结尾偏移地址为 15,而 15 不是 4 的倍数,所以要补位(补至恰好是 min 倍数即可,这里 4 * 3 < 15 而 4 * 4 > 15 所以补至 16),因此偏移 16,占用空间 16-19

    第三步,分析结构体大小

    • 目前整个结构体占用空间 0-19 即 20B,20 是对齐值 4 的倍数,所以不需填充,所以最终大小是 20B

    回到示例一

    再把例子重新放上来吧,以免往上翻

    class B
    {
        public:
            char b;                 // 8
            virtual void fun() {};  // 8
            static int c;           // 0
            static int d;           // 0
            static int f;           // 0
    };
    
    cout<<sizeof(B)<<endl; // 16
    

    之前说char b对齐 8B 是因为对齐最宽的虚指针长度,其实是不对的。我测试了对齐值分别为 4 和 8 两种情况,测试结果不相同

    // ======================== v1 =============================
    #pragma pack(4)                 // 指定对齐值 4
    class B
    {
    public:
    	char b;                 // 4
    	virtual void fun() {};  // 8
    	static int c;           // 0
    	static int d;           // 0
    	static int f;           // 0
    };
    
    cout<<sizeof(B)<<endl; // 12
    
    // ================== v2(同示例一) ========================
    #pragma pack(8)                 // 指定对齐值 8,64-bit 貌似默认 8,这也是一开始示例一的默认对齐值
    class B
    {
    public:
    	char b;                 // 8
    	virtual void fun() {};  // 8
    	static int c;           // 0
    	static int d;           // 0
    	static int f;           // 0
    };
    
    cout<<sizeof(B)<<endl; // 16
    

    可以发现两种结构体char b对齐 4B 和 8B,因此可以得出结论简单地说对齐最长成员是不对的

    回到示例二

    这里我也测试了 4 和 8 两种对齐值的情况,但让人意外的是,测试结果稍微有点超出我的预期。所以我目前还不敢确定自己的看法是否正确,仅作参考,也欢迎指正

    // ======================== v1 =============================
    #pragma pack(4)                 // 指定对齐值 4
    class A
    {
        public:
            char a; // 4
            int b;  // 4
    };
    class C
    {
        A a;        // 8
        char c;     // 4
    };
    cout<<sizeof(C)<<endl; // 12
    
    // ======================== v2 =============================
    #pragma pack(8)                 // 指定对齐值 8
    class A
    {
        public:
            char a; // 4
            int b;  // 4
    };
    class C
    {
        A a;        // 8
        char c;     // 4
    };
    cout<<sizeof(C)<<endl; // 12
    

    是不是和预期不同?

    • 第一个版本对齐值 4,min = min{对齐系数(4),最长成员(8)} = 4,结构体长度简单累加起来是 12B,12B 是 4 的倍数,所以不用填充。这没问题,符合我们上面的结论
    • 但第二个版本对齐值 8,min = min{对齐系数(8),最长成员(8)} = 8,结构体长度简单累加起来是 12B。按我们上面的结论 12B 并不是 8 的倍数,理应填充,但运行结果却没填充,所以我认为,类对象作为成员计算长度时,并不应该计算整个类的长度,而是把类的成员拆开来看取其中最长的那个
  • 相关阅读:
    php 下设置cookie问题
    js 页面无滚动条添加滚轮事件
    Python中关于字符串的问题
    Python 字符串相加问题
    ajax 同步和异步
    重叠div鼠标经过事件
    Myeclipse中将项目上传到码云
    eclipse debug的时候提示debug Edit Source Lookup path
    阿里云+wordpress搭建个人博客网站
    centos7 安装mysql
  • 原文地址:https://www.cnblogs.com/bEngi1/p/15876707.html
Copyright © 2020-2023  润新知