结构体与联合体 (Structs, Unions & Enums)


章节概述

结构体(struct)是 C 语言组织异构数据的聚合工具——将不同数据类型的变量组合成一个逻辑整体。联合体(union)则让多个成员共享同一块内存,是 C 语言实现”类型双关”(type punning)和节省内存的关键机制。枚举(enum)为整数常量提供有意义的名称。本章从 struct 的基本定义和成员访问出发,深入讲解 typedef 的类型别名、结构体的内存对齐(alignment)和填充(padding)——这是理解内存布局和 sizeof 结果的关键底层概念——以及联合体的内存共享原理、枚举的整数本质和位域(bit-field)的精细内存控制。最后从汇编层面展示结构体成员相对于基址的偏移寻址(base + offset),建立完整的内存布局心智模型。

核心主题:struct = 内存布局的艺术。理解 padding 就理解了为什么你的 sizeof(struct) 比想象中大;理解 alignment 就理解了 CPU 访问内存的效率瓶颈。本章内容直接关联后续的面向对象模拟(07_面向对象C编程)和数据结构实现([[|]])。


第一节:结构体定义与使用


1.1 struct 的基本语法

#include <stdio.h>
#include <string.h>
 
// 定义结构体类型
struct Student {
    char name[32];
    int age;
    double score;
};  // 注意分号!
 
int main() {
    // 声明结构体变量
    struct Student s1;
 
    // 初始化(按声明顺序)
    strcpy(s1.name, "张三");
    s1.age = 20;
    s1.score = 95.5;
 
    // 声明 + 初始化(C99 指定初始化器)
    struct Student s2 = {
        .name = "李四",
        .age = 22,
        .score = 88.0
    };
 
    // 顺序初始化(不推荐,可读性差)
    struct Student s3 = {"王五", 19, 92.0};
 
    // 成员访问:点运算符
    printf("姓名: %s, 年龄: %d, 成绩: %.1f\n",
           s1.name, s1.age, s1.score);
 
    // 结构体赋值(整体复制,包括数组成员)
    struct Student s4 = s1;  // 栈上 memcpy
    s4.name[0] = 'A';         // 修改副本不影响原值
    printf("s1.name = %s, s4.name = %s\n", s1.name, s4.name);
 
    return 0;
}

1.2 指向结构体的指针(箭头运算符)

#include <stdio.h>
#include <stdlib.h>
 
struct Point {
    int x;
    int y;
};
 
int main() {
    struct Point p1 = {10, 20};
 
    // 指向结构体的指针
    struct Point *ptr = &p1;
 
    // 两种访问方式(等价)
    printf("(*ptr).x = %d\n", (*ptr).x);   // 解引用 + 点运算
    printf("ptr->x  = %d\n", ptr->x);       // 箭头运算(推荐)
    printf("ptr->y  = %d\n", ptr->y);
 
    // 通过指针修改成员
    ptr->x = 100;
    ptr->y = 200;
    printf("修改后: (%d, %d)\n", p1.x, p1.y);
 
    // 堆上分配结构体
    struct Point *heap_p = malloc(sizeof(struct Point));
    if (heap_p) {
        heap_p->x = 50;
        heap_p->y = 60;
        printf("堆上: (%d, %d)\n", heap_p->x, heap_p->y);
        free(heap_p);
    }
 
    return 0;
}

1.3 结构体数组

#include <stdio.h>
 
struct Book {
    char title[50];
    int pages;
    double price;
};
 
int main() {
    // 结构体数组
    struct Book library[3] = {
        {"C 语言程序设计", 450, 59.9},
        {"算法导论", 1312, 128.0},
        {"编译原理", 800, 89.0}
    };
 
    // 遍历
    for (int i = 0; i < 3; i++) {
        printf("《%s%d页 ¥%.1f\n",
               library[i].title,
               library[i].pages,
               library[i].price);
    }
 
    // 修改
    library[0].price = 49.9;
    strcpy(library[2].title, "深入理解计算机系统");
 
    // 结构体数组在内存中的布局:
    // 每个结构体元素连续排列
    printf("\nsizeof(struct Book) = %zu\n", sizeof(struct Book));
    printf("数组总大小 = %zu\n", sizeof(library));
    // library[i] 的地址 = library + i * sizeof(struct Book)
 
    return 0;
}

小节练习

选择题 1

访问结构体指针 p 指向的成员 x,正确的语法是?

  • A. p.x

  • B. p->x

  • C. *p.x

  • D. &p.x

判断题 1

两个相同类型的结构体变量可以直接用 = 赋值(整体复制)。 ( )

  • 正确

  • 错误


第二节:typedef 与结构体


2.1 typedef 的基本用法

#include <stdio.h>
 
// 方式1:先定义 struct,再用 typedef 取别名
struct Point {
    int x;
    int y;
};
typedef struct Point Point_t;
 
// 方式2:定义 + typedef 合一(最常见)
typedef struct {
    int x;
    int y;
} Point;  // 这里 Point 是类型别名,不是 struct 标签
 
// 方式3:同时有标签和别名
typedef struct Vector {
    int x;
    int y;
    int z;
} Vector;
 
int main() {
    // 使用时不需要写 struct 关键字
    Point p1 = {10, 20};
    Vector v1 = {1, 2, 3};
 
    // 如果需要前向引用(链表),必须保留标签
    typedef struct Node {
        int data;
        struct Node *next;  // 这里必须用 struct Node
    } Node;
 
    Node head = {42, NULL};
    printf("Node data: %d\n", head.data);
 
    return 0;
}

2.2 链表节点 —— typedef + 自引用结构体

#include <stdio.h>
#include <stdlib.h>
 
// 链表节点(自引用结构体)—— 必须保留 struct 标签
typedef struct ListNode {
    int value;
    struct ListNode *next;  // 不能写 ListNode *next!typedef 还没完成
} ListNode;
 
// 创建新节点
ListNode *create_node(int value) {
    ListNode *node = malloc(sizeof(ListNode));
    if (node) {
        node->value = value;
        node->next = NULL;
    }
    return node;
}
 
// 打印链表
void print_list(ListNode *head) {
    for (ListNode *p = head; p != NULL; p = p->next) {
        printf("[%d] -> ", p->value);
    }
    printf("NULL\n");
}
 
int main() {
    ListNode *head = create_node(1);
    head->next = create_node(2);
    head->next->next = create_node(3);
 
    print_list(head);
 
    // 清理内存
    while (head) {
        ListNode *next = head->next;
        free(head);
        head = next;
    }
 
    return 0;
}

链表的完整实现在 02_链表 中详细展开。

小节练习

选择题 1

以下自引用结构体定义正确的是?

  • A. typedef struct { int d; Node *n; } Node;

  • B. typedef struct Node { int d; struct Node *n; } Node;

  • C. typedef struct { int d; struct Node *n; } Node;

  • D. typedef Node { int d; Node *n; };


第三节:内存对齐与填充(Memory Alignment & Padding)


3.1 什么是内存对齐

CPU 访问内存时,对齐的数据比不对齐的数据更快(甚至必须对齐)。对齐规则:

  • char 可以在任意地址(对齐 1 字节)
  • short 必须对齐到 2 的倍数地址
  • int 必须对齐到 4 的倍数地址
  • double 必须对齐到 8 的倍数地址(x86-64)
#include <stdio.h>
 
int main() {
    printf("=== 基本类型的对齐要求 ===\n");
    printf("_Alignof(char)      = %zu\n", _Alignof(char));      // 1
    printf("_Alignof(short)     = %zu\n", _Alignof(short));     // 2
    printf("_Alignof(int)       = %zu\n", _Alignof(int));       // 4
    printf("_Alignof(long)      = %zu\n", _Alignof(long));      // 8
    printf("_Alignof(float)     = %zu\n", _Alignof(float));     // 4
    printf("_Alignof(double)    = %zu\n", _Alignof(double));    // 8
    printf("_Alignof(long long) = %zu\n", _Alignof(long long)); // 8
 
    return 0;
}

3.2 结构体填充(Padding)

编译器在结构体成员之间和结构体末尾插入不可见的填充字节,以确保每个成员满足其对齐要求。

#include <stdio.h>
#include <stddef.h>
 
// 案例1:未优化的布局(浪费空间)
struct BadLayout {
    char  a;    // 1B, offset 0
    // padding 3B (offset 1 不整除 4)
    int   b;    // 4B, offset 4
    char  c;    // 1B, offset 8
    // padding 7B (让结构体大小整除最大对齐 8)
    double d;   // 8B, offset 16
};
// sizeof(BadLayout) = 24 字节!(padding 占了 10 字节)
 
// 案例2:优化后的布局(节省空间)
struct GoodLayout {
    double d;   // 8B, offset 0
    int   b;    // 4B, offset 8
    char  a;    // 1B, offset 12
    char  c;    // 1B, offset 13
    // padding 2B (让结构体大小整除 8)
};
// sizeof(GoodLayout) = 16 字节!(节省 8 字节)
 
int main() {
    printf("sizeof(BadLayout)  = %zu\n", sizeof(struct BadLayout));
    printf("sizeof(GoodLayout) = %zu\n", sizeof(struct GoodLayout));
 
    // 用 offsetof 查看每个成员的偏移
    printf("\n=== BadLayout 偏移 ===\n");
    printf("offsetof(a) = %zu\n", offsetof(struct BadLayout, a));  // 0
    printf("offsetof(b) = %zu\n", offsetof(struct BadLayout, b));  // 4
    printf("offsetof(c) = %zu\n", offsetof(struct BadLayout, c));  // 8
    printf("offsetof(d) = %zu\n", offsetof(struct BadLayout, d));  // 16
 
    printf("\n=== GoodLayout 偏移 ===\n");
    printf("offsetof(d) = %zu\n", offsetof(struct GoodLayout, d));  // 0
    printf("offsetof(b) = %zu\n", offsetof(struct GoodLayout, b));  // 8
    printf("offsetof(a) = %zu\n", offsetof(struct GoodLayout, a));  // 12
    printf("offsetof(c) = %zu\n", offsetof(struct GoodLayout, c));  // 13
 
    return 0;
}

3.3 内存布局可视化

graph TD
    subgraph "BadLayout (24 字节)"
        direction LR
        B0["offset 0<br/>a (char) 1B"]
        B1["offset 1-3<br/>PADDING 3B"]
        B2["offset 4-7<br/>b (int) 4B"]
        B3["offset 8<br/>c (char) 1B"]
        B4["offset 9-15<br/>PADDING 7B"]
        B5["offset 16-23<br/>d (double) 8B"]
    end
    B0 --> B1 --> B2 --> B3 --> B4 --> B5

    subgraph "GoodLayout (16 字节)"
        direction LR
        G0["offset 0-7<br/>d (double) 8B"]
        G1["offset 8-11<br/>b (int) 4B"]
        G2["offset 12<br/>a (char) 1B"]
        G3["offset 13<br/>c (char) 1B"]
        G4["offset 14-15<br/>PADDING 2B"]
    end
    G0 --> G1 --> G2 --> G3 --> G4

对齐规则总结

  1. 每个成员的偏移必须是其对齐要求(alignment)的倍数
  2. 结构体的总大小必须是其最大成员对齐要求的倍数(含结构体末尾的填充)
  3. 成员排列顺序影响结构体的总大小
  4. _Alignas(N) 可以手动指定对齐(C11)

3.4 手动控制对齐

#include <stdio.h>
#include <stddef.h>
 
// C11 _Alignas 指定对齐
struct AlignedExample {
    char c;
    _Alignas(16) int x;   // x 的对齐要求是 16 字节
};
 
// GCC 扩展:__attribute__((packed)) 取消对齐(节省空间但可能影响性能)
struct __attribute__((packed)) Packed {
    char  a;
    int   b;
    char  c;
    double d;
};
// sizeof(Packed) = 14(完全没有 padding!)
// 但访问 b 和 d 很慢(不对齐的访问)
 
// 编译指示(pragma pack)
#pragma pack(push, 1)     // 设为 1 字节对齐
struct PackedByPragma {
    char  a;
    int   b;     // 没有 padding,offset = 1
    double d;    // 没有 padding,offset = 5
};
#pragma pack(pop)         // 恢复默认对齐
// sizeof = 13
 
int main() {
    printf("sizeof(AlignedExample) = %zu\n", sizeof(struct AlignedExample));
    printf("sizeof(Packed)         = %zu\n", sizeof(struct Packed));
    printf("sizeof(PackedByPragma)  = %zu\n", sizeof(struct PackedByPragma));
    return 0;
}

对齐和填充是 C 语言”深入底层”的典型体现。C++ 的 struct 与 class 在内存布局上与 C struct 完全相同(多出了 vtable 指针)。比较见 CPP教程: struct结构体

小节练习

选择题 1

x86-64 上,struct { char a; int b; } 的大小是?

  • A. 5 字节

  • B. 6 字节

  • C. 8 字节

  • D. 12 字节

选择题 2

减少结构体填充的最佳方法是?

  • A. 使用 #pragma pack(1)

  • B. 按对齐要求从大到小排列成员

  • C. 全部使用 char 类型

  • D. 使用 __attribute__((packed))


第四节:联合体(union)


4.1 union 的基本概念

联合体所有成员共享同一块内存,任何时候只有一个成员是”活跃”的。

#include <stdio.h>
 
// 联合体的大小 = 最大成员的大小
union Data {
    int   i;    // 4 字节
    float f;    // 4 字节
    char  s[8]; // 8 字节
};
 
int main() {
    union Data d;
    printf("sizeof(union Data) = %zu\n", sizeof(d));  // 8
 
    // 写入 i,覆盖了前 4 字节
    d.i = 42;
    printf("d.i = %d\n", d.i);
 
    // 写入 f,覆盖了前 4 字节(i 的值丢失)
    d.f = 3.14f;
    printf("d.f = %f\n", d.f);
    printf("d.i = %d(被覆盖后的垃圾值)\n", d.i);
 
    // 写入字符串(最多 7 个字符 + \0)
    strcpy(d.s, "Hello");
    printf("d.s = %s\n", d.s);
 
    return 0;
}
graph TD
    subgraph "union Data 内存布局(8 字节)"
        direction LR
        U0["Byte 0"]
        U1["Byte 1"]
        U2["Byte 2"]
        U3["Byte 3"]
        U4["Byte 4"]
        U5["Byte 5"]
        U6["Byte 6"]
        U7["Byte 7"]
    end
    subgraph "成员映射"
        I["int i: 使用 Byte 0-3"]
        F["float f: 使用 Byte 0-3"]
        S["char s#91;8#93;: 使用 Byte 0-7"]
    end

4.2 union 的典型应用

#include <stdio.h>
#include <string.h>
 
// 应用1:类型双关(Type Punning)—— 重新解释位模式
union FloatInt {
    float f;
    unsigned int u;
};
 
void show_float_bits(float value) {
    union FloatInt fi;
    fi.f = value;
    printf("float %f 的位模式: 0x%08X\n", value, fi.u);
}
 
// 应用2:变体记录(Tagged Union)
enum DataType { TYPE_INT, TYPE_FLOAT, TYPE_STRING };
 
struct Variant {
    enum DataType type;
    union {
        int i_val;
        double f_val;
        char s_val[32];
    } data;
};
 
void print_variant(struct Variant v) {
    switch (v.type) {
        case TYPE_INT:
            printf("整数: %d\n", v.data.i_val);
            break;
        case TYPE_FLOAT:
            printf("浮点: %f\n", v.data.f_val);
            break;
        case TYPE_STRING:
            printf("字符串: %s\n", v.data.s_val);
            break;
    }
}
 
// 应用3:寄存器访问(嵌入式常用)
// 32 位寄存器可以整体访问,也可以按位域访问
union Register {
    unsigned int full;
    struct {
        unsigned int enable   : 1;   // bit 0
        unsigned int mode     : 2;   // bit 1-2
        unsigned int speed    : 2;   // bit 3-4
        unsigned int reserved : 27;  // bit 5-31
    } bits;
};
 
int main() {
    // 类型双关演示
    show_float_bits(3.14f);
    show_float_bits(-1.0f);
 
    // 变体记录演示
    struct Variant v1 = {TYPE_INT, .data.i_val = 42};
    struct Variant v2 = {TYPE_FLOAT, .data.f_val = 3.14};
    struct Variant v3 = {TYPE_STRING, .data.s_val = "Hello"};
    print_variant(v1);
    print_variant(v2);
    print_variant(v3);
 
    // 寄存器访问演示
    union Register reg;
    reg.full = 0;
    reg.bits.enable = 1;
    reg.bits.mode = 3;
    printf("寄存器值: 0x%08X\n", reg.full);
 
    return 0;
}

C++ 使用 std::variant(C++17)提供了类型安全的变体类型,而 C 语言需要手动维护标签变量。union 的基本思想在 C++ 中仍然用于底层内存操作。

小节练习

选择题 1

union { char c; int i; double d; } 占用多少字节?

  • A. 1

  • B. 4

  • C. 8

  • D. 13


第五节:枚举(enum)与位域(bit-field)


5.1 enum 枚举类型

#include <stdio.h>
 
// 枚举:为整数常量提供有意义的名称
enum Color {
    RED,      // 0
    GREEN,    // 1
    BLUE      // 2
};
 
// 手动指定值
enum Status {
    OK = 200,
    NOT_FOUND = 404,
    SERVER_ERROR = 500,
    UNKNOWN     // 501(自动递增前一个值)
};
 
// 二进制位标志
enum Permissions {
    READ    = 1 << 0,  // 0b001 = 1
    WRITE   = 1 << 1,  // 0b010 = 2
    EXECUTE = 1 << 2   // 0b100 = 4
};
 
int main() {
    // enum 变量本质是整数
    enum Color c = RED;
    printf("RED = %d\n", c);
 
    // enum 可以用在 switch 中
    enum Color favorite = BLUE;
    switch (favorite) {
        case RED:   printf("红色\n"); break;
        case GREEN: printf("绿色\n"); break;
        case BLUE:  printf("蓝色\n"); break;
    }
 
    // 位标志组合
    int my_perms = READ | WRITE;  // 3
    printf("权限: %d\n", my_perms);
    if (my_perms & READ) {
        printf("有读权限\n");
    }
 
    // 遍历枚举
    for (enum Color col = RED; col <= BLUE; col++) {
        printf("Color %d\n", col);
    }
 
    return 0;
}

5.2 位域(Bit-field)

位域允许精确控制结构体成员占用的位数,常用于协议头和硬件寄存器。

#include <stdio.h>
#include <string.h>
 
// 位域示例:IPv4 数据包头(简化版)
struct IPv4Header {
    unsigned int version     : 4;   // 4 位
    unsigned int ihl         : 4;   // 4 位(首部长度)
    unsigned int dscp        : 6;   // 6 位(服务类型)
    unsigned int ecn         : 2;   // 2 位
    unsigned int total_length: 16;  // 16 位
};
 
// 位域示例:CPU 状态寄存器
struct StatusRegister {
    unsigned int carry     : 1;  // bit 0
    unsigned int zero      : 1;  // bit 1
    unsigned int sign      : 1;  // bit 2
    unsigned int overflow  : 1;  // bit 3
    unsigned int           : 4;  // 未命名位域(占位,跳过 bit 4-7)
    unsigned int interrupt : 1;  // bit 8
};
 
int main() {
    // 位域结构体大小
    printf("sizeof(IPv4Header)     = %zu\n", sizeof(struct IPv4Header));
    printf("sizeof(StatusRegister) = %zu\n", sizeof(struct StatusRegister));
 
    // 使用位域
    struct IPv4Header header;
    memset(&header, 0, sizeof(header));
    header.version = 4;      // IPv4
    header.ihl = 5;          // 最小头长度(5 × 4 = 20 字节)
    header.total_length = 1500;
 
    printf("version = %u, ihl = %u, total_length = %u\n",
           header.version, header.ihl, header.total_length);
 
    // 状态寄存器
    struct StatusRegister sr = {0};
    sr.carry = 1;
    sr.zero = 0;
    // 位域可以用作位掩码的标志替代
    printf("carry=%u, zero=%u\n", sr.carry, sr.zero);
 
    // 位域的汇编访问:
    // 编译器将位域操作翻译为位掩码 + 移位
    // header.version = 4 → 需要读取、掩码、移位、写回
    // 比单独的结构体成员访问稍慢
 
    return 0;
}

5.3 位域的局限和注意事项

#include <stdio.h>
 
int main() {
    // 局限1:不能对位域取地址(没有独立的地址)
    struct {
        unsigned int flag : 1;
    } s;
 
    // unsigned int *p = &s.flag;  // 编译错误!
    // 因为位域可能跨字节边界,没有独立的地址
 
    // 局限2:位域的大小和排列是"实现定义"的
    // 在不同编译器/平台上可能不同(大端/小端)
 
    // 局限3:位域不能用于数组
    // struct { unsigned int arr[3] : 4; };  // 编译错误
 
    // 局限4:不能做 sizeof 获取位宽(sizeof 返回的是包含类型的大小)
    printf("sizeof unsigned int: %zu\n", sizeof(unsigned int));
 
    return 0;
}

小节练习

选择题 1

枚举 enum { A, B = 5, C, D = 2, E }; 中 E 的值是?

  • A. 0

  • B. 3

  • C. 6

  • D. 7


第六节:结构体在汇编层面的访问


6.1 结构体成员的汇编寻址

// C 代码
struct Point {
    int x;   // offset 0
    int y;   // offset 4
};
 
void set_point(struct Point *p, int x, int y) {
    p->x = x;
    p->y = y;
}

对应 x86-64 汇编(简化):

set_point:
    movl    %esi, (%rdi)       ; p->x = x
    ; %rdi = p (基址), %esi = x
    ; (%rdi) = *p = p->x(offset 0)
 
    movl    %edx, 4(%rdi)      ; p->y = y
    ; 4(%rdi) = *(p + 4) = p->y(offset 4)
    ret

结构体成员访问的关键:编译器将 p->member 翻译为 *(base + offset) 的寻址模式。

6.2 复杂结构体的寻址

struct Inner {
    int a;       // offset 0
    double b;    // offset 8 (padding 4)
};  // sizeof = 16
 
struct Outer {
    char c;       // offset 0
    // padding 7
    struct Inner inner;  // offset 8
    int d;        // offset 24
};  // sizeof = 32 (末尾 padding 4)
 
// C 代码
void set_inner_b(struct Outer *o, double val) {
    o->inner.b = val;
}

汇编:

set_inner_b:
    ; o->inner.b 的偏移 = offsetof(struct Outer, inner) + offsetof(struct Inner, b)
    ;                  = 8 + 8 = 16
    movsd   %xmm0, 16(%rdi)   ; o->inner.b = val (8 字节浮点)
    ret

offsetof 宏在编译时计算成员的字节偏移,是结构体操作的基础。汇编层面的间接寻址详细讨论见 。

小节练习

选择题 1

struct { char a; int b; char c; }c 的偏移是?

  • A. 1

  • B. 5

  • C. 8

  • D. 9


章节测试

一、判断题(正确选,错误选)

判断题 1

C 语言中 structclass 的区别是 struct 不能有成员函数。 ( )

  • 正确

  • 错误

判断题 2

结构体成员在内存中一定按声明的顺序排列。 ( )

  • 正确

  • 错误

判断题 3

union 的所有成员可以同时持有有效值。 ( )

  • 正确

  • 错误

判断题 4

sizeof(struct) 总是等于所有成员 sizeof 之和。 ( )

  • 正确

  • 错误

判断题 5

枚举类型的变量在内存中存储的是字符串(枚举名)。 ( )

  • 正确

  • 错误

判断题 6

位域(bit-field)成员不能使用 sizeof 运算符。 ( )

  • 正确

  • 错误

判断题 7

typedef struct { int x; } Point; 之后 Point 就是 struct Point 的别名。 ( )

  • 正确

  • 错误

判断题 8

结构体的整体赋值 s1 = s2 使用的是浅拷贝(只复制指针值)。 ( )

  • 正确

  • 错误

判断题 9

联合体可以用来判断系统是大端还是小端(端序检测)。 ( )

  • 正确

  • 错误

判断题 10

_Alignas 可以要求某个成员的偏移少于其自然对齐。 ( )

  • 正确

  • 错误


二、选择题(单项选择题)

选择题 1

访问结构体指针 p 的成员 name,最简洁的写法是?

  • A. (*p).name

  • B. p.name

  • C. p->name

  • D. *p.name

选择题 2

x86-64 上 struct { double a; char b; } 的大小是?

  • A. 9

  • B. 12

  • C. 16

  • D. 17

选择题 3

offsetof(struct S, member) 返回什么?

  • A. member 的大小

  • B. member 在结构体中的字节偏移

  • C. 结构体总大小

  • D. member 的对齐值

选择题 4

以下关于 union 的说法哪项正确?

  • A. union 可以有多个活跃成员

  • B. union 的大小是全部成员大小之和

  • C. union 的所有成员起始于同一地址

  • D. union 不能包含结构体成员

选择题 5

自引用结构体在定义 next 成员时,必须使用什么?

  • A. typedef 别名

  • B. struct 标签

  • C. void 指针

  • D. 无所谓

选择题 6

enum { X = 2, Y, Z = 6, W }; 中 W 的值是?

  • A. 3

  • B. 4

  • C. 5

  • D. 7

选择题 7

union { int i; char c[4]; } u; u.i = 0x01020304; 在小端机器上 u.c[0] 的值是?

  • A. 0x01

  • B. 0x02

  • C. 0x03

  • D. 0x04

选择题 8

以下哪个声明同时创建了结构体标签和 typedef 别名?

  • A. struct Point { int x,y; } typedef Point;

  • B. typedef struct Point { int x,y; } Point;

  • C. typedef { int x,y; } Point;

  • D. struct typedef Point { int x,y; };

选择题 9

在 x86-64 上,struct { int a; } s;&s.a&s 的关系是?

  • A. &s.a > &s

  • B. &s.a == &s

  • C. &s.a < &s

  • D. 不确定

选择题 10

汇编访问 ptr->member(offset = 8)最可能的指令是?

  • A. mov (%rdi), %eax

  • B. mov 8(%rdi), %eax

  • C. lea 8(%rdi), %eax

  • D. add $8, %rdi


️ 动手练习题

练习题 1:学生成绩管理系统

难度:

定义一个 Student 结构体(姓名、学号、3 门课成绩),实现:

  1. 输入 5 名学生的信息
  2. 计算每个学生的总分和平均分
  3. 按总分降序排序(需要交换整个结构体)
  4. 用表格形式对齐输出(使用宽度格式化)

力扣练习:力扣数组题

练习题 2:联合体端序检测

难度:

使用 union 编写程序检测:

  1. 你的系统是大端还是小端
  2. floatint 的位模式对比
  3. 用 union 实现一个简单的类型转换器(int ↔ float ↔ 4 个 unsigned char)

力扣练习:力扣数组比较题

练习题 3:内存对齐实验

难度:

定义多个包含不同类型成员的结构体,用 sizeofoffsetof 观察:

  1. 相同成员、不同顺序的结构体大小差异
  2. _Alignas 对结构体大小的影响
  3. #pragma pack(1) 对结构体大小的影响
  4. 绘制每个结构体的内存布局图(ASCII 图)

练习题 4:位域实现 IPv4 包头解析

难度:

用位域结构体实现一个简化的 IPv4 数据包头解析器:

  1. 用位域定义 IPv4 包头结构(版本、IHL、DSCP、ECN、总长度、TTL、协议等字段)
  2. 从原始字节序列中解析出包头(使用 union 将字节数组映射为结构体)
  3. 打印每个字段的值
  4. 观察大端和小端下的差异

力扣练习:力扣位操作题

练习题 5:变体标签结构体

难度:

用 enum + union 实现一个简单的变体类型系统:

  1. 定义一个 Value 结构体,包含一个 enum 类型标签和一个 union
  2. 支持 int、double、char* 三种类型
  3. 实现 print_value() 函数打印任意类型的值
  4. 实现 Value add(Value a, Value b) 在类型匹配时执行加法
  5. 类型不匹配时通过标签进行错误处理

参考 CPP教程: struct结构体 了解 C++ 中 std::variant 的类型安全变体机制。