自定义数据类型

结构体(字段)

  • 结构体的声明方法

    • 一般的结构体声明格式

      struct tag {
          member - list;
      }variable - list;

      注:在创建了sturct tag的变量类型后,还会附带创建variable-list中的变量为此类型的全局变量

    • 声明匿名结构体

      struct {
          member - list;
      }variable-list;

      注:

      • 因为类型没有名字,所以匿名结构体类型只能在variable-list中创建
      • 两个成员都相同的结构体的情况下,编译器也会把他们当成不同的结构体类型

      匿名结构体的定义可以这样写:

      struct {
          int a;
          char b;
          float c;
      } arr[20], *ptr;
  • 结构体嵌套结构体

    • 写法

      struct node {
        int data;
        struct node* next;
      };
    • 错误写法

      struct Node {
        int data;
        struct Node next; // 此处错误
      };
  • 结构体的内存对齐

    • 内存对齐的意义

      • 跨平台:不是所有的硬件平台都能访问任意地址上的任意数据的。某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
      • 性能优化:为了访问未对齐的内存,处理器需要作两次内存访问。而对齐的内存访问仅需要一次访问
      • 总的来说,就是空间换时间
    • 结构体内存对齐规则

      • 基本对齐单位:每个成员有自身对齐值(通常为类型大小,如 char=1、int=4)

        默认对齐数:编译器有默认对齐系数(如 VS 默认 8,GCC 默认 4),成员实际对齐值取「自身类型大小」与「默认对齐数」的较小值

        优先级:当指定 #pragma pack (n) 时,默认对齐数被 n 替代,成员实际对齐值取「类型大小」与「n」的较小值

      • 结构体整体对齐值为成员最大对齐值

        整体对齐调整:结构体最终总大小是「所有成员实际对齐值的最大值」的整数倍,不足则填充

      • 成员存放规则:每个成员起始地址(偏移量)需是自身对齐值的整数倍(含0),不足则填充字节

        注:第一个成员的偏移量定义为0

      • 整体对齐规则:结构体总大小需是整体对齐值的整数倍,不足则在末尾填充

      • 嵌套结构体:内部结构体的对齐值为其自身最大成员对齐值,整体仍遵循上述规则

    • 补充

      “不足则填充” 是内存对齐中为满足对齐规则而插入空白字节的过程,可从两个层面理解

      • 成员间填充 当某个成员的起始地址不符合其对齐要求时,在该成员前插入空白字节(填充),直到地址满足 “是自身对齐值整数倍” 的条件

        例:struct { char a; int b; } 中,char 占 1 字节,int 对齐值为 4。a 存储在地址 0 后,b 需从地址 4 开始,因此在 a 和 b 之间填充 3 个空白字节(地址 1-3)

      • 结构体末尾填充 结构体总大小需是整体对齐值(所有成员对齐值的最大值)的整数倍。若计算的总大小不满足,在结构体末尾补充空白字节

        例:上述结构体中,成员总大小为 1(a)+3(填充)+4(b)=8,恰好是整体对齐值 4 的 2 倍,无需末尾填充;若成员为 char a; short b(对齐值 2),总大小 1+1(填充)+2=4,满足 2 的整数倍要求

        填充的空白字节不存储有效数据,仅用于保证内存对齐,以提升 CPU 访问效率


位段

  • 概念

    位段是基于结构体的空间优化数据类型,核心作用是节省内存空间,常见于底层开发(如网络开发、硬件驱动),通过指定成员占用的二进制位数,减少普通结构体因内存对齐产生的空间浪费

  • 位段与结构体差异

    • 成员类型限制

      位段成员必须是整型相关类型(int、unsigned int、signed int,实际测试中char等整型也可)

    • 位数指定规则

      成员名后需紧跟冒号(:)和数字,数字表示该成员占用的二进制位数

    • 分配逻辑

      即使总位数未超过单个int的 32 位,若后续成员无法容纳,仍会开辟新的int空间(如_d需要 30 位,第一个空间仅剩 15 位,无法容纳,因此开辟第二个 32 位空间)

  • 成员存储细节

    • 成员按声明顺序依次存储在开辟的空间中
    • 若当前空间剩余位数不足以容纳下一个成员,是否利用剩余空间无 C 标准定义(取决于编译器)
    • 成员在内存中的存储方向(从左到右 / 从右到左)无标准定义,属于编译器相关行为
  • 声明示例

    struct A {
        int _a : 2; // _a 占用 2 个二进制位
        int _b : 5; // _b 占用 5 个二进制位
        int _c : 10; // _c 占用 10 个二进制位
        int _d : 30; // _d 占用 30 个二进制位
    };
    // 上述位段总占用位数:2 + 5 + 10 + 30 = 47 bit(约 6 字节)
  • 位段的不确定性

    • int位段的符号性:int位段被当作有符号还是无符号整数,标准未定义
    • 最大位数限制:不同平台(16 位 / 32 位 / 64 位)对整型位数限制不同,例如 16 位机器上,位段成员指定 27 bit 会超出 int(16 bit)范围,导致错误
    • 剩余空间利用:前一个成员存储后剩余的空间,是否用于存储下一个成员,无标准定义
    • 存储方向:成员在内存中按左到右还是右到左分配,无标准定义
« 内存管理的库函数介绍 ← 返回列表 文件管理的库函数介绍 »