• C语言结构体字节对齐与gcc手动设置对齐__attribute__((aligned(n)))和__attribute__((packed))


    一、字节对齐规则

    【规则一】数据成员对齐规则:变量只能从他的长度的整数倍地址开始存储

    第一个数据成员放在 offset 为 0的地方,以后每个数据成员的对齐按照操作系统的基本字节单位(32位操作系统为4,64位操作系统为8)和这个数据成员自身长度中,比较小的那个进行。
    即以后每个数据成员放在 offset= \(min(操作系统的基本字节单位,当前数据成员长度)×正整数\)

    【规则二】整体对齐规则:跟最大数据成员长度的整数倍对齐

    在数据成员完成各自对齐之后,结构体(或联合体)本身也要进行对齐。
    所有结构体成员的字节长度 没有超出(<=) 操作系统的基本字节单位(32位操作系统为4,64位操作系统为8),按照结构体中字节数最大的变量长度来对齐
    结构体中某个成员的字节长度 超出(>) 操作系统基本字节单位,按照系统基本字节单位来对齐

    【规则三】结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素长度的整数倍地址开始存储。

    从操作系统的基本字节单位(32位操作系统为4,64位操作系统为8)和其内部最大元素长度两者中取较小值,从该值的整数倍地址开始存储。
    offset= \(min(操作系统的基本字节单位,结构体成员内部最大数据成员长度)×正整数\)
    特别地,如果结构体成员指定了__attribute__((aligned(n))),那么从n的整数倍地址开始存储。(见“七、嵌套结构体”的例子)

    二、attribute((aligned(n)))

    __attribute__((aligned(n)))中,n的有效参数为2的幂值,32位最大为\(2^{32}\),64位为 \(2^{64}\),这个时候编译器会将让n与默认的对齐字节数进行比较,取较大值为对齐字节数,与#pragma pack(n)恰好相反。

    它的作用是让整个结构体变量整体进行n字节对齐(注意是结构体变量整体n字节对齐,而不是结构体内各数据成员也要n字节对齐)

    三、attribute((packed)) 取消编译时对齐优化

    __attribute__((packed)) 为取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,也就是采用1字节对齐。

    四、attribute()在结构体类型中的使用方法

    __attribute__() 的位置比较灵活

    定义结构体时不对类型重命名,即不使用 typedef 时:

    struct mystruct
    {
        /*成员变量定义*/
    }__attribute__() /*(可同时在这定义变量)*/;
    
    struct __attribute__() mystruct
    {
        /*成员变量定义*/
    }/*(可同时在这定义变量)*/;
    

    定义结构体同时对类型进行重命名,即使用 typedef 时:

    typedef struct mystruct
    {
        /*成员变量定义*/
    }__attribute__() mystruct_t;
    
    typedef struct __attribute__() mystruct
    {
        /*成员变量定义*/
    } mystruct_t;
    

    五、测试代码

    #include <stdio.h>
    
    typedef struct A {
      int v1;
      double v2;
      char v3;
    } A_t;
    
    typedef struct B {
      int v1;
      double v2;
      char v3;
    }__attribute__((aligned(4))) B_t;
    
    typedef struct __attribute__((aligned(16))) C {
      int v1;
      double v2;
      char v3;
    } C_t;
    
    typedef struct __attribute__((packed)) D {
      int v1;
      double v2;
      char v3;
    } D_t;
    
    int main() {
      printf("%d\n", (int)sizeof(A_t));
      printf("%d\n", (int)sizeof(B_t));
      printf("%d\n", (int)sizeof(C_t));
      printf("%d\n", (int)sizeof(D_t));
    
      A_t a;
      a.v1 = 0x11;
      a.v2 = 0x1;
      a.v3 = 0xa;
    
      B_t b;
      b.v1 = 0x22;
      b.v2 = 0x2;
      b.v3 = 0xb;
    
      C_t c;
      c.v1 = 0x33;
      c.v2 = 0x3;
      c.v3 = 0xc;
    
      D_t d;
      d.v1 = 0x44;
      d.v2 = 0x4;
      d.v3 = 0xd;
      return 0;
    }
    

    执行编译指令:

    geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ gcc -m32 main.c -o main.32.o
    geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ gcc main.c -o main.o

    执行结果如下:

    geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ ./main.o
    24
    24
    32
    13
    geekziyu@geekziyu-ubuntu-1:~/CLionProjects/c-helloworld$ ./main.32.o
    16
    16
    16
    13

    5.1 64位程序

    结构体 A_t a 对象结构如下图所示:

    a.v1 从偏移地址0开始存储,int占4个字节 【规则一】
    a.v2 是double类型,长度为8个字节,64位程序默认对齐数也是8个字节,因此从8的整数倍偏移地址开始存储,即 offset=8【规则一】
    a.v3 是char类型,长度为1个字节,64位程序默认对齐数是8个字节,因此可以从 offset=9 开始存储 【规则一】
    接着,当前结构体 A_t a 整体 应该按照64位默认对齐数8B来对齐,即整体大小是8的整数倍
    现在已经有17个字节了,因此,通过对齐填充扩展到size=24 【规则二】
    0x3ff0000000000000 等于double型的1

    结构体 B_t b 对象结构如下图所示:

    __attribute__((aligned(4))) 要求结构体数据成员对齐;
    0x4000000000000000 等于double型的2

    结构体 C_t c 对象结构如下图所示:

    0x4008000000000000 等于double型的3

    结构体 D_t d 对象结构如下图所示:

    按一个字节对齐是最简单,结构也是最紧凑的,没有对齐填充的部分。
    0x4010000000000000 等于double型的4

    5.2 32位程序

    结构体 A_t a 对象结构如下图所示:

    a.v2 是double类型,长度为8个字节,32位程序默认对齐数是4个字节,取较小值4B,因此从4的整数倍偏移地址开始存储,即 offset=4【规则一】
    整体是按4B还是8B对齐暂时不好判断,需要增加实验。

    结构体 B_t b 对象结构如下图所示:

    整体是按4B还是8B对齐暂时不好判断,需要增加实验。

    结构体 C_t c 对象结构如下图所示:

    整体是按16B还是8B对齐暂时不好判断,需要增加实验。

    结构体 D_t d 对象结构取消了对齐,因此结构和64位程序一样。

    六、分析与验证

    6.1 规则一与操作系统的基本字节单位有关

    例6.1-1:把以下程序编译为64位程序并运行:

    typedef struct A {
      char v1;
      int v2;
    } A_t;
    
    int main() {
      A_t a;
      a.v1 = 0x11;
      a.v2 = 0x22;
      return 0;
    }
    

    预期结果分析:
    结构体成员变量 i 长度为 4B,编译为64位程序,则基本字节单位为 8B,取较小值 4B,因此它从偏移地址 4 开始存储,即 offset=4

    根据 GDB 分析后,该结构体在栈上的存储如下图所示:

    例6.1-2:把以下程序编译为32位程序并运行:

    #include <stdio.h>
    
    typedef struct A {
      char v1;
      double v2;
    } A_t;
    
    int main() {
      printf("%d\n", (int) sizeof(A_t)); // 输出结果为12
      A_t a;
      a.v1 = 0x11;
      a.v2 = 0x2;
      return 0;
    }
    

    预期结果分析:
    结构体成员变量 v2 程度是 8B,编译为64位程序,则基本字节单位为 4B,取较小值 4B,因此它从偏移地址 4 开始存储,即 offset=4

    根据 GDB 分析后,该结构体在栈上的存储如下图所示:

    6.2 规则一与__attribute__((aligned(n)))无关

    上一节6.1的结构体都加上 __attribute__((aligned(n))),第二个成员的起始偏移地址 offset 也不会发生变化

    6.3 规则二不是32位系统就按照4字节对齐,64位系统就按照8字节对齐

    typedef struct A {
        char v1;
        char v2;
        char v3;
    } A_t;
    

    上述程序在32位和64位操作系统下,sizeof(A_t)都等于3。而并非32位系统下等于4,64位系统下等于8

    这个例子就说明:如果 所有结构体成员的长度 没有超出(<=) 操作系统的基本字节单位(32位操作系统为4,64位操作系统为8),按照结构体中字节数最大的变量长度来对齐

    6.4 规则二并不总是按最大成员长度对齐

    typedef struct A {
      char v1;
      double v2;
    } A_t;
    

    上述程序在32位操作系统下,sizeof(A_t) 等于12。正好是32位系统基本字节单位4B的整数倍,而非最大数据成员长度8B的整数倍16。

    这个例子就说明:结构体中某个成员的字节长度 超出(>) 操作系统基本字节单位,按照系统基本字节单位来对齐。

    6.5 规则二与__attribute__((aligned(n)))的关联

    我在结构体内就放一个成员变量,然后在32位和64位上测试 sizeof() 的结果填写到单元格中,以下是汇总表格:

    结构体内成员变量类型 __attribute__((aligned(n))) 32位 64位 备注
    char 不设置 1 1 32位和64位的sizeof的结果都可以证明
    对齐字节数是从 操作系统基本字节单元 和 最大数据成员长度 中选取较小值
    char n=4 4 4
    char n=8 8 8
    char n=16 16 16
    char n=32 32 32
    int 不设置 4 4
    int n=4 4 4 64位的sizeof的结果相对比较明显地证明
    对齐字节数是从 操作系统基本字节单元 和 最大数据成员长度 中选取较小值
    int n=8 8 8
    int n=16 16 16
    int n=32 32 32

    当你没有设置 __attribute__((aligned(n))) 时,对齐字节数是从 操作系统基本字节单元 和 最大数据成员长度 中选取较小值;
    但是,当你设置了 __attribute__((aligned(n))) 之后,整体就得按 n 来对齐。

    七、嵌套结构体

    在64位操作系统下编译运行以下程序:

    #include <stdio.h>
    
    typedef short t;
    
    typedef struct A {
      t v1;
      char v2;
    } A_t;
    
    typedef struct B {
      char v1;
      A_t v2;
    } B_t;
    
    typedef struct __attribute__((aligned(32)))C {
      char v1;
      A_t v2;
    } C_t;
    
    int main() {
      printf("%d\n", (int)sizeof(A_t));
      printf("%d\n", (int)sizeof(B_t));
      printf("%d\n", (int)sizeof(C_t));
    
      A_t a;
      a.v1 = 0x1;
      a.v2 = 0x11;
    
      B_t b;
      b.v1 = 0x22;
      b.v2 = a;
    
      C_t c;
      c.v1 = 0x33;
      c.v2 = a;
    
      return 0;
    }
    

    结构体 A_t a 的内存结构如下图:

    结构体 B_t b 的内存结构如下图:

    结构体 C_t c 的内存结构如下图:

    B_t bC_t c 第二个成员结构体变量,都是从 2B 的整数倍地址开始的,2B 是 操作系统基本字节单元8B 和结构体 A_t 中的最大成员长度2B 中的较小值。
    B_t bC_t c 分布基本相同,差别是 C_t c 的对齐填充更多。

    如果把结构体 A 改为整体 8B 对齐:

    typedef struct __attribute__((aligned(8))) A {
        t v1;
        char v2;
    } A_t;
    

    结构体 A_t a 的内存结构如下图:

    结构体 B_t b 的内存结构如下图:

    结构体 C_t c 的内存结构如下图:

    B_t bC_t c 第二个成员结构体变量,按照 8B 的整数倍地址开始,显然是因为__attribute__((aligned(8))) 指定的 8B。
    为了验证 __attribute__((aligned(8))) 的“绝对成立性”,我又去编译了32位程序,结果还是从 8B 的整数倍地址开始!
    B_t bC_t c 分布基本相同,差别是 C_t c 的对齐填充更多。

    参考文档

    attribute((aligned(n)))和__attribute__((packed))

  • 相关阅读:
    python libsvm模块在mac上的安装
    logistic regression (Python&Matlab实现)
    读C++ primer 的一些练习
    R笔记
    python学习笔记
    linux常用命令
    macbook air安装ubuntu双系统简记
    解方程——对分法
    解方程——不动点迭代
    隐马尔可夫模型模型评估及最优路径的matlab实现
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/16551501.html
Copyright © 2020-2023  润新知