这一章在干嘛?

结构的声明、成员访问(. 与 ->)、自引用、存储对齐,结构传参的取舍,以及位段与联合两个「贴着硬件」的特性。

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 访问对齐数据更快,某些平台不对齐直接崩:

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 = 大端 —— 探测字节序的经典写法 */

变体记录套路:

结构体里放「类型标签 + 联合」,标签决定这次联合按哪个成员解读——没有运行时类型信息时,靠它区分数据种类。