📖 第 10 章:结构和联合(Structures and Unions)

🔤 Kenneth Reek《Pointers on C》(C和指针)· Chapter 10
🗨️ 本章金句:「结构是把相关数据捆成一团的绳子;联合是同一块内存的多种解读方式。」
这一章在干嘛? 结构的声明、成员访问(. 与 ->)、自引用、存储对齐,结构传参的取舍,以及位段与联合两个「贴着硬件」的特性。
10.1 结构基础:声明、访问、自引用10.2 存储分配与对齐10.3 结构作函数参数:传值还是传指针?10.4 位段与联合

10.1 结构基础:声明、访问、自引用

typedef struct Node {          /* typedef 起名,后面直接用 Node */
    int         value;
    struct Node *next;         /* 自引用:只能是指针(大小未知前不能整体嵌入) */
} Node;

Node n = { 10, NULL };         /* 初始化列表按成员顺序 */
Node *p = &n;

n.value          /* 直接访问:结构.成员 */
p->value         /* 间接访问:指针->成员,等价于 (*p).value */

两个不同结构类型的指针不能直接互赋(类型不同),尽管它们都是「地址」——编译器在做类型检查。不完整声明 struct B; 用于解决两个结构互相引用的死锁(A 里放 B*,B 里放 A*)。

10.2 存储分配与对齐

结构成员按声明顺序排列,但中间可能塞「填充字节」让每个成员落在其「对齐边界」上——CPU 访问对齐数据更快,某些平台不对齐直接崩:

$$\text{成员偏移} \equiv 0 \pmod{\text{min(成员对齐要求, 编译器默认对齐)}} \qquad \text{结构总大小} = \text{最大对齐值的整数倍}$$
struct Bad  { char c;  double d;  char e; };  /* 排布:c(1)+填7+d(8)+e(1)+填7 = 24 */
struct Good { double d; char c;  char e; };   /* d(8)+c(1)+e(1)+填6 = 16 */

两条实用法则:① 成员按大小降序排列能省空间;② 不要比较结构体本身(填充字节是随机值,== 不可用,memcmp 也不可靠),逐成员比较才对。

10.3 结构作函数参数:传值还是传指针?

方式开销能否修改原结构适用
void f(struct S s)拷贝整个结构(大的很贵)小型只读结构,想要值语义隔离
void f(struct S *ps)一个指针能(const 修饰可禁写)大结构、需要回写、链表操作——默认选它
void grow(struct Rect *r, int dw) { r->w += dw; }      /* 回写用指针 */
int  area(const struct Rect *r)   { return r->w * r->h; } /* 只读加 const */

返回结构也是传值拷贝,小结构返回值很方便;大结构返回用「调用方传入输出指针」更高效。

10.4 位段与联合

位段(bit field):把成员指定到 bit 级,硬件寄存器/协议头打包利器:

struct Flags {
    unsigned ready  : 1;   /* 1 bit */
    unsigned mode   : 3;   /* 0~7 */
    unsigned channel: 4;   /* 0~15 */
};                          /* 总共 1 字节出头(对齐后通常 1~2 字节) */

联合(union):所有成员共享同一起始地址,大小 = 最大成员。同一块内存的多种解读:

union Reg {
    unsigned int u32;      /* 整体读 */
    unsigned char b[4];    /* 按字节读:看字节序 */
};
union Reg r;
r.u32 = 0x12345678;
printf("%02X\n", r.b[0]);  /* 78 = 小端;12 = 大端 —— 探测字节序的经典写法 */
变体记录套路: 结构体里放「类型标签 + 联合」,标签决定这次联合按哪个成员解读——没有运行时类型信息时,靠它区分数据种类。
🧠 小测验
1. p->value 等价于什么?为什么需要两种访问符?
等价于 (*p).value。. 用于结构变量直接访问成员,-> 用于结构指针间接访问。因为 * 的优先级低于 .,(*p).value 必须加括号,-> 让间接访问更简洁且不易写错。
2. 为什么不能直接用 == 比较两个结构体?
运算符 == 不支持结构类型(编译报错)。而 memcmp 也不可靠:结构内的填充字节内容不确定,两个成员完全相同的结构可能因填充不同而判不等。正确做法是逐成员比较。
3. 联合 union 的核心特性与典型用途?
所有成员共享同一块内存(起始地址相同),大小等于最大成员,写一个成员会覆盖其他成员的解读。典型用途:多视角解读同一数据(如探测字节序、寄存器字/字节访问)、变体记录(配合类型标签节省内存)。
← 上一篇🏠 顶层目录下一篇 →