计算机基础:ADT(Abstract Data Type)抽象数据类型 (2)

1.5.4 延迟绑定

延迟绑定(Lazy Binding)是动态链接器优化,将外部函数地址的解析推迟到第一次调用时。通过PLT(过程链接表)和GOT(全局偏移表)实现,本质上是函数指针的数组,在调用时动态更新。

在动态链接的程序中,延迟绑定是一种优化技术:外部函数(如 printf、malloc)的实际地址不是在程序启动时解析,而是在第一次被调用时才由动态链接器确定。

这通过 PLT(Procedure Linkage Table) 和 GOT(Global Offset Table) 配合实现,本质上就是函数指针表的运行时动态更新------GOT 中保存的是真正的函数地址(初始指向 PLT 中的一段解析代码),第一次调用时动态链接器会填充正确的地址,之后直接通过 GOT 跳转。

1.5.4.1 例子

C++ 复制代码
// lazy.c
#include <stdio.h>

int main() {
    printf("第一次调用 printf\n");
    printf("第二次调用 printf\n");
    return 0;
}

1.5.4.2 分析

在动态链接的程序中,外部函数(如 puts)的地址并不在程序启动时就确定,而是在第一次调用时由动态链接器解析并填充到 GOT 表中。

  • PLT(过程链接表):存放一小段代码,负责从 GOT 中读取函数地址并跳转。

  • GOT(全局偏移表):存放实际函数地址的数组,初始时指向 PLT 中的解析器,第一次调用后更新为真实地址。

Assembly 复制代码
puts@plt:
    adrp x16, GOT_page       ; 获取 GOT 页基址
    ldr  x17, [x16, #offset] ; 从 GOT 条目加载地址到 x17
    add  x16, x16, #something
    br   x17                 ; 间接跳转到该地址
Assembly 复制代码
编译:gcc -g -O0 -o lazy lazy.c(默认延迟绑定)

禁用 ASLR(可选,便于地址重现):
bash
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
Assembly 复制代码
gdb ./lazy
break main                     # 断点1:停在 main 入口
break *0x0000aaaaaaaa063c      # 断点2:停在 puts@plt 中的 br x17 指令

注:0xaaaaaaaa063c 是 br x17 的地址,可以通过 disas puts@plt 查看

Assembly 复制代码
##程序会执行到第一次 printf(实际是 puts)的 PLT 桩,并在 br x17 指令处停下:
Breakpoint 2, 0x0000aaaaaaaa063c in puts@plt ()
Assembly 复制代码
info register x16          # 查看 GOT 页基址(此时已经过 add 指令)
info register x17          # 应该是一个解析器地址(如 0xaaaaaaaa05d0)
x/gx $x16                  # 查看 GOT 条目内容,应与 x17 相同

##预期输出:
x17    0xaaaaaaaa05d0x/gx $x16 -> 0xaaaaaaaa05d0

单步执行 br x17,进入动态链接器

Assembly 复制代码
stepi

程序会跳转到 0xaaaaaaaa05d0(动态链接器内部,无源码)。这证实了间接跳转。

在真正的 puts 函数设置断点,继续执行

Assembly 复制代码
break puts
continue

动态链接器完成后会调用 puts,程序停在 puts 断点:

C 复制代码
Breakpoint 3, __GI__IO_puts (str=0xaaaaaaaa07a0 "第一次调用 printf") ...

结论:第一次调用完成,且 GOT 已被更新。

从 puts 返回,继续执行到第二次调用

C 复制代码
finish         # 从 puts 返回到 maincontinue       # 继续执行,遇到第二次 printf 的 puts@plt

程序再次停在 br x17 断点(第二次调用)。

观察第二次调用时的状态(解析后)

Assembly 复制代码
info register x17          # 应为 puts 的真实地址
x/gx $x16                  # GOT 条目内容也应相同

##预期输出:
x17    0xffff7e61af0x/gx 
$x16 -> 0xffff7e61af0

继续执行,程序正常结束

Assembly 复制代码
continue

输出 第二次调用 printf,程序退出。

关键点讲解

步骤 观察内容 技术意义
第一次 br x17 前 x17 为解析器地址(如 0xaaaaaaaa05d0) GOT 未初始化,指向 PLT 内的解析桩
stepi 后 跳转到解析器地址 间接跳转实现回调(框架调用用户代码)
break puts 命中 动态链接器找到符号并调用 延迟绑定的符号解析完成
第二次 br x17 前 x17 为真实函数地址(如 0xffff7e61af0) GOT 已被更新,后续直接调用
x/gx $x16 GOT 内容与 x17 一致 验证 GOT 就是函数指针表

1.5.4.3 三种跳转指令对比

bl Branch with Link 直接跳转(相对地址偏移) ✅ 是(pc+4 存入 x30) 调用普通函数(编译时地址已知)
blr Branch with Link to Register 间接跳转(从寄存器取地址) ✅ 是(pc+4 存入 x30) 调用函数指针、回调、虚函数
br Branch to Register 间接跳转(从寄存器取地址) ❌ 否 用于实现 switch 跳转表、ret(br x30)、长跳转
  1. bl (直接调用)
  • 目标地址由 PC 相对偏移 编码在指令中,编译时确定。

  • 适合调用静态链接的函数或动态库中的已知 PLT 桩。

  • 示例:bl printf 或 bl 0x1234。

  1. blr (间接调用 + 保存返回地址)
  • 从寄存器(如 x0-x30)中读取目标地址。

  • 同时将下一条指令的地址写入 x30(链接寄存器)。

  • 适合所有函数指针调用、C++ 虚函数、回调函数等运行时才能确定地址的场景。

  • 示例:blr x1 或 blr x16。

  1. br (纯间接跳转)
  • 从寄存器中读取目标地址,不保存返回地址。

  • 常用于:

    • 函数返回:ret 实际是 br x30 的别名。

    • 状态机跳转表:从表中加载地址到 x16 然后 br x16。

    • 实现 goto 或尾调用优化。

  • 示例:br x8、ret(即 br x30)。

1.5.5 设计模式

设计模式的核心思想之一是"将变化的部分与不变的部分分离"。在 C 语言中,函数指针是实现这一目标的有力工具,它允许我们将行为作为参数传递给框架,或在不同对象间共享相同的接口。

1.5.5.1 策略模式(Strategy Pattern)

将不同的算法 / 逻辑封装成独立函数,通过函数指针动态选择执行。

解决:大量 if-else、代码耦合、无法动态切换逻辑

应用:计算器、协议解析、设备驱动、状态机

1.5.5.1.1 例子
C++ 复制代码
// strategy.c
#include <stdio.h>

// 策略函数类型:两个整数,返回整数
typedef int (*strategy_t)(int, int);

// 具体策略:加法
int add(int a, int b) { return a + b; }

// 具体策略:减法
int sub(int a, int b) { return a - b; }

// 具体策略:乘法
int mul(int a, int b) { return a * b; }

// 上下文:使用策略的客户
int execute_strategy(strategy_t strat, int x, int y) {
    return strat(x, y);  // 通过函数指针调用具体算法
}

int main() {
    int x = 10, y = 5;

    printf("10 + 5 = %d\n", execute_strategy(add, x, y));
    printf("10 - 5 = %d\n", execute_strategy(sub, x, y));
    printf("10 * 5 = %d\n", execute_strategy(mul, x, y));

    // 运行时动态切换策略
    strategy_t current = add;
    printf("current: 10 + 5 = %d\n", execute_strategy(current, x, y));
    current = mul;
    printf("switch to mul: 10 * 5 = %d\n", execute_strategy(current, x, y));

    return 0;
}
分析
Java 复制代码
(gdb) p execute_strategy
$3 = {int (strategy_t, int, int)} 0xaaaaaaaa07b8 <execute_strategy>
(gdb) p add
$4 = {int (int, int)} 0xaaaaaaaa0758 <add>
(gdb) p sub
$5 = {int (int, int)} 0xaaaaaaaa0778 <sub>
(gdb) p mul
$6 = {int (int, int)} 0xaaaaaaaa0798 <mul>


execute_strategy (strat=0xaaaaaaaa0758 <add>, x=10, y=5) at strategy.c:18

execute_strategy (strat=0xaaaaaaaa0758 <add>, x=10, y=5) at strategy.c:18

函数指针strat指向add,将add函数首地址传入execute_strategy函数,return strat(x, y)相当于return add(x,y);

Assembly 复制代码
//add函数首地址
(gdb) p add
$2 = {int (int, int)} 0xaaaaaaaa0758 <add>

//execute_strategy函数首地址
Dump of assembler code for function execute_strategy:
   0x0000aaaaaaaa07b8 <+0>:        stp        x29, x30, [sp, #-32]!

//execute_strategy(add, x, y)->跳转到add函数首地址
   0x0000aaaaaaaa07d8 <+32>:        blr        x2

//此时x2中的值,是add函数的首地址
(gdb) info register x2
x2             0xaaaaaaaa0758      187649984431960

1.5.5.2 命令模式(Command Pattern)

将请求封装为一个对象(在 C 语言中为函数指针 + 参数),从而允许将请求参数化、排队、记录日志、支持撤销等。

1.5.5.2.1 例子
C++ 复制代码
// command.c
#include <stdio.h>
#include <stdlib.h>

// 命令结构体:包含函数指针和参数数据
typedef struct command {
    void (*execute)(void*);  // 执行函数
    void* data;              // 命令携带的数据
} command_t;

// 具体命令:打印整数
void print_int(void* d) { //void* 参数接收的是"任意类型对象的地址"
    int* p = (int*)d;     //*将 void* 转回 int**
    printf("print_int: %d\n", *p);//*解引用,得到int值*
}

// 具体命令:打印字符串
void print_str(void* d) { //void* 参数接收的是"任意类型对象的地址"
    char* s = (char*)d;  //*将 void* 转回 char**
    printf("print_str: %s\n", s);//*解引用,得到char值*
}

// 命令队列(简化)
#define MAX_CMDS 10
command_t cmd_queue[MAX_CMDS];
int cmd_count = 0;

void add_command(command_t cmd) {
    if (cmd_count < MAX_CMDS) cmd_queue[cmd_count++] = cmd;
}

void execute_all_commands() {
    for (int i = 0; i < cmd_count; i++) {
        cmd_queue[i].execute(cmd_queue[i].data);  // 间接调用
    }
}

int main() {
    int a = 42, b = 100;
    char* msg = "Hello Command!";

    // 构造命令并加入队列
    add_command((command_t){print_int, &a});
    add_command((command_t){print_str, msg});
    add_command((command_t){print_int, &b});

    // 批量执行
    execute_all_commands();
    return 0;
}
1.5.5.2.2 分析
Java 复制代码
(gdb) p print_int
$1 = {void (void *)} 0xaaaaaaaa0858 <print_int>
(gdb) p print_str
$2 = {void (void *)} 0xaaaaaaaa0890 <print_str>
(gdb) p add_command
$3 = {void (command_t)} 0xaaaaaaaa08c0 <add_command>
(gdb) p execute_all_commands
$4 = {void ()} 0xaaaaaaaa0920 <execute_all_commands>


函数print_int,print_str,add_command,execute_all_commands
在0xaaaaaaaa0000~0xaaaaaaaa1000 权限r-xp(可读、可执行)代码段。

初始化变量。

disas main:

disas add_command:

disas execute_all_commands:

Assembly 复制代码
    add_command((command_t){print_int, &a});
    add_command((command_t){print_str, msg});
    add_command((command_t){print_int, &b});
C 复制代码
正确使用** void* **的通用模式

通常我们约定:传入的 void* 数据必须与函数内部转换的指针类型实际匹配。例如:
print_int 应该只接收 int* 转换来的 void*。
print_str 应该只接收 char* 转换来的 void*。
这种匹配靠程序员保证,编译器无法检查。

//错误操作:
double pi = 3.14;
print_int(&pi);   // 将 double* 转为 void*,内部转为 int* 再解引用 → 危险!
//
在实际编程中(如命令模式、回调函数),通过 void* 携带数据是非常常见的技巧,
但必须严格遵守类型契约:哪个函数用哪种数据类型,就应该只传入该类型的地址。如果混用,后果自负。
Assembly 复制代码
//main 函数调用add_command
   
   0x0000aaaaaaaa09dc <+68>:        adrp        x2, 0xaaaaaaaa0000
   0x0000aaaaaaaa09e0 <+72>:        add        x0, x2, #0x858
   0x0000aaaaaaaa09e4 <+76>:        add        x2, sp, #0x8
   0x0000aaaaaaaa09e8 <+80>:        mov        x1, x2
   0x0000aaaaaaaa09ec <+84>:        bl        0xaaaaaaaa08c0 <add_command>
   
   0x0000aaaaaaaa09f0 <+88>:        adrp        x0, 0xaaaaaaaa0000
   0x0000aaaaaaaa09f4 <+92>:        add        x20, x0, #0x890
   0x0000aaaaaaaa09f8 <+96>:        ldr        x21, [sp, #16]
   0x0000aaaaaaaa09fc <+100>:        mov        x0, x20
   0x0000aaaaaaaa0a00 <+104>:        mov        x1, x21
   0x0000aaaaaaaa0a04 <+108>:        bl        0xaaaaaaaa08c0 <add_command>
   
   0x0000aaaaaaaa0a08 <+112>:        adrp        x0, 0xaaaaaaaa0000
   0x0000aaaaaaaa0a0c <+116>:        add        x22, x0, #0x858
   0x0000aaaaaaaa0a10 <+120>:        add        x0, sp, #0xc
   0x0000aaaaaaaa0a14 <+124>:        mov        x23, x0
   0x0000aaaaaaaa0a18 <+128>:        mov        x0, x22
   0x0000aaaaaaaa0a1c <+132>:        mov        x1, x23
   0x0000aaaaaaaa0a20 <+136>:        bl        0xaaaaaaaa08c0 <add_command>

//----------------------------------------------------------------------------
$1 = {void (void *)} 0xaaaaaaaa0858 <print_int>
$2 = {void (void *)} 0xaaaaaaaa0890 <print_str>
$3 = {void (command_t)} 0xaaaaaaaa08c0 <add_command>
Assembly 复制代码
void add_command(command_t cmd) {
    if (cmd_count < MAX_CMDS) cmd_queue[cmd_count++] = cmd;
}

#0xaaaaaaaa08c0 <add_command>入口地址

   0x0000aaaaaaaa08c0 <+0>:        sub        sp, sp, #0x10
   0x0000aaaaaaaa08c4 <+4>:        stp        x0, x1, [sp]
   0x0000aaaaaaaa08c8 <+8>:        adrp        x0, 0xaaaaaaac0000
   0x0000aaaaaaaa08cc <+12>:        add        x0, x0, #0xb8
   0x0000aaaaaaaa08d0 <+16>:        ldr        w0, [x0]
   
#如果 cmd_count > 9,
   0x0000aaaaaaaa08d4 <+20>:        cmp        w0, #0x9
   
#则跳转到 +84(即函数末尾的 nop; ret),跳过添加操作。
#对应 C 语句 if (cmd_count < MAX_CMDS) 的否定条件。
   0x0000aaaaaaaa08d8 <+24>:        b.gt        0xaaaaaaaa0914 <add_command+84>
   
   0x0000aaaaaaaa08dc <+28>:        adrp        x0, 0xaaaaaaac0000
   0x0000aaaaaaaa08e0 <+32>:        add        x0, x0, #0xb8
   0x0000aaaaaaaa08e4 <+36>:        ldr        w0, [x0]
   
   0x0000aaaaaaaa08e8 <+40>:        add        w2, w0, #0x1
   0x0000aaaaaaaa08ec <+44>:        adrp        x1, 0xaaaaaaac0000
   0x0000aaaaaaaa08f0 <+48>:        add        x1, x1, #0xb8
   0x0000aaaaaaaa08f4 <+52>:        str        w2, [x1]
   
   0x0000aaaaaaaa08f8 <+56>:        adrp        x1, 0xaaaaaaac0000
   0x0000aaaaaaaa08fc <+60>:        add        x1, x1, #0x18
   0x0000aaaaaaaa0900 <+64>:        sxtw        x0, w0
   0x0000aaaaaaaa0904 <+68>:        lsl        x0, x0, #4
   0x0000aaaaaaaa0908 <+72>:        add        x2, x1, x0
   0x0000aaaaaaaa090c <+76>:        ldp        x0, x1, [sp]
   0x0000aaaaaaaa0910 <+80>:        stp        x0, x1, [x2]
   

   0x0000aaaaaaaa0914 <+84>:        nop
   0x0000aaaaaaaa0918 <+88>:        add        sp, sp, #0x10
   0x0000aaaaaaaa091c <+92>:        ret
Plain 复制代码
(gdb) p cmd_queue
$15 = {{execute = 0xaaaaaaaa0858 <print_int>, data = 0xffffffffeca8}, 
       {execute = 0xaaaaaaaa0890 <print_str>, data = 0xaaaaaaaa0aa8}, 
       {execute = 0xaaaaaaaa0858 <print_int>, data = 0xffffffffecac}, 
       {execute = 0x0, data = 0x0}, 
       {execute = 0x0, data = 0x0},
       {execute = 0x0, data = 0x0}, 
       {execute = 0x0, data = 0x0}, 
       {execute = 0x0, data = 0x0}, 
       {execute = 0x0, data = 0x0}, 
       {execute = 0x0, data = 0x0}}
 
//将print_int,print_str,print_int三个任务添加到对列。
C 复制代码
void add_command(command_t cmd) {
    if (cmd_count < MAX_CMDS) cmd_queue[cmd_count++] = cmd;
}

//cmd_count=2,第三个任务

(gdb) p print_int
$16 = {void (void *)} 0xaaaaaaaa0858 <print_int>
(gdb) p cmd
$17 = {execute = 0xaaaaaaaa0858 <print_int>, data = 0xffffffffecac}
(gdb) p cmd.execute
$18 = (void (*)(void *)) 0xaaaaaaaa0858 <print_int>
(gdb) p cmd.data
$19 = (void *) 0xffffffffecac
(gdb) p cmd_count
$20 = 3

//--------------------------------------------------------------
add_command((command_t){print_int, &a}); 展开是:

command_t cmd = {print_int, &a};
或:
command_t cmd;
cmd.execute = print_int;
cmd.data = &a;

add_command(cmd);
//-------------------------------------------------------------
command_t cmd  -->任务

//任务的数据结构
typedef struct command {
    void (*execute)(void*);  // 执行函数 --> md_queue[2].execute -->print_int
    void* data;              // 命令携带的数据 --> cmd_queue[2].data --> &b
} command_t;

add_command((command_t){print_int, &b});
add_command(command_t cmd)  --> add 第三个任务 cmd_queue[2]-->{print_int, &b}


//执行第三个任务
execute_all_commands  --> cmd_queue[2].execute(cmd_queue[2].data); 
//第三个任务的数据结构
md_queue[2].execute -->print_int
cmd_queue[2].data --> &b
//第三个任务
print_int(&b)
1.5.5.2.3 命令模式和策略模式的区别
角度 策略模式 (Strategy) 命令模式 (Command)
目的 封装算法,使其可以互换 封装请求(动作 + 参数),使其可存储、排队、撤销
关注点 "如何做" (How) "做什么" (What) + "何时做" (When)
调用关系 上下文主动调用策略 调用者(Invoker)触发命令,命令执行者(Receiver)完成实际工作
参数传递 通常通过函数参数传递(如 execute_strategy(strat, x, y)) 参数与命令对象绑定(如 {print_int, &a}),调用时无需额外传参
是否支持撤销 一般不涉及 常常支持(存储状态,提供 undo())
典型例子 排序算法、压缩算法、税费计算 GUI 按钮点击、任务队列、事务回滚
  • 策略模式:函数指针 + 参数(数据由调用方提供)
C 复制代码
typedef int (*strategy_t)(int, int);

--> execute_strategy(add, x, y)

  • 命令模式:函数指针 + 数据指针(数据与函数捆绑成对象)
C 复制代码
typedef struct command {
    void (*execute)(void*);  // 执行函数 --> md_queue[i].execute -->print_int
    void* data;              // 命令携带的数据 --> cmd_queue[i].data --> &b
} command_t;

add_command((command_t){print_int, &a});

cmd_queuei.execute(cmd_queuei.data);

状态模式(State Pattern)

状态模式允许对象在内部状态改变时改变其行为。在 C 语言中,状态模式通常通过函数指针表(状态表)实现:每个状态对应一组操作函数,状态切换时只需改变指向当前状态函数表的指针。

例子

功能说明

  • 门有三种状态:关闭(CLOSED)、打开(OPEN)、锁定(LOCKED)。

  • 支持三个操作:open、close、lock、unlock。

  • 不同状态下对相同操作的反应不同。例如:

    • 关闭状态下 open 会切换到打开状态;lock 会切换到锁定状态。

    • 锁定状态下 open / close 无效;unlock 会切换到关闭状态。

    • 打开状态下 close 切换到关闭状态;lock 无效(或提示先关闭)。

C++ 复制代码
// state_door_simple.c
#include <stdio.h>

typedef struct Door Door;

// 操作函数指针类型
typedef void (*op_func_t)(Door*);

// 状态结构体:包含该状态下的四个操作函数
typedef struct DoorState {
    op_func_t open;
    op_func_t close;
    op_func_t lock;
    op_func_t unlock;
    const char* name;
} DoorState;

// 门结构体:持有当前状态
struct Door {
    const DoorState* state;
};

// 前向声明状态实例
extern const DoorState STATE_CLOSED;
extern const DoorState STATE_OPEN;
extern const DoorState STATE_LOCKED;

// 状态函数实现
static void open_closed(Door* d) {
    printf("当前状态: %s, 执行 open -> 切换到 OPEN\n", d->state->name);
    d->state = &STATE_OPEN;
}
static void close_closed(Door* d) {
    printf("当前状态: %s, 执行 close -> 无变化 (已关闭)\n", d->state->name);
}
static void lock_closed(Door* d) {
    printf("当前状态: %s, 执行 lock -> 切换到 LOCKED\n", d->state->name);
    d->state = &STATE_LOCKED;
}
static void unlock_closed(Door* d) {
    printf("当前状态: %s, 执行 unlock -> 无变化 (未锁定)\n", d->state->name);
}

static void open_open(Door* d) {
    printf("当前状态: %s, 执行 open -> 无变化 (已打开)\n", d->state->name);
}
static void close_open(Door* d) {
    printf("当前状态: %s, 执行 close -> 切换到 CLOSED\n", d->state->name);
    d->state = &STATE_CLOSED;
}
static void lock_open(Door* d) {
    printf("当前状态: %s, 执行 lock -> 无效 (请先关闭)\n", d->state->name);
}
static void unlock_open(Door* d) {
    printf("当前状态: %s, 执行 unlock -> 无变化 (未锁定)\n", d->state->name);
}

static void open_locked(Door* d) {
    printf("当前状态: %s, 执行 open -> 无效 (已锁定)\n", d->state->name);
}
static void close_locked(Door* d) {
    printf("当前状态: %s, 执行 close -> 无效 (已锁定)\n", d->state->name);
}
static void lock_locked(Door* d) {
    printf("当前状态: %s, 执行 lock -> 无变化 (已锁定)\n", d->state->name);
}
static void unlock_locked(Door* d) {
    printf("当前状态: %s, 执行 unlock -> 切换到 CLOSED\n", d->state->name);
    d->state = &STATE_CLOSED;
}

// 定义状态表实例
const DoorState STATE_CLOSED = {
    .open = open_closed,
    .close = close_closed,
    .lock = lock_closed,
    .unlock = unlock_closed,
    .name = "CLOSED"
};
const DoorState STATE_OPEN = {
    .open = open_open,
    .close = close_open,
    .lock = lock_open,
    .unlock = unlock_open,
    .name = "OPEN"
};
const DoorState STATE_LOCKED = {
    .open = open_locked,
    .close = close_locked,
    .lock = lock_locked,
    .unlock = unlock_locked,
    .name = "LOCKED"
};

// 门操作封装(通过当前状态调用)
void door_open(Door* d) { d->state->open(d); }
void door_close(Door* d) { d->state->close(d); }
void door_lock(Door* d) { d->state->lock(d); }
void door_unlock(Door* d) { d->state->unlock(d); }

int main() {
    Door d;
    d.state = &STATE_CLOSED;

    printf("=== 门状态机演示 ===\n");
    door_open(&d);   // CLOSED -> OPEN
    door_open(&d);   // OPEN 中再打开: 无效
    door_close(&d);  // OPEN -> CLOSED
    door_lock(&d);   // CLOSED -> LOCKED
    door_open(&d);   // LOCKED: 无效
    door_unlock(&d); // LOCKED -> CLOSED
    door_open(&d);   // CLOSED -> OPEN
    door_close(&d);  // OPEN -> CLOSED

    return 0;
}
1.5.5.3.2 分析:
C 复制代码
//0xaaaaaaaa0000~0xaaaaaaaa2000 权限:r-xp 代码段
0xaaaaaaaa0000  0xaaaaaaaa2000 0x2000 0x0 r-xp /home/parallels/my/ADT/function/state

(gdb) p door_open
$1 = {void (Door *)} 0xaaaaaaaa0c88 <door_open>
(gdb) p door_close
$2 = {void (Door *)} 0xaaaaaaaa0cb4 <door_close>
(gdb) p door_lock
$3 = {void (Door *)} 0xaaaaaaaa0ce0 <door_lock>
(gdb) p door_unlock
$4 = {void (Door *)} 0xaaaaaaaa0d0c <door_unlock>

(gdb) p STATE_LOCKED
$5 = {open = 0xaaaaaaaa0ba8 <open_locked>, close = 0xaaaaaaaa0bdc <close_locked>, 
      lock = 0xaaaaaaaa0c10 <lock_locked>, unlock = 0xaaaaaaaa0c44 <unlock_locked>, 
      name = 0xaaaaaaaa1090 "LOCKED"}
(gdb) p STATE_OPEN
$6 = {open = 0xaaaaaaaa0ac8 <open_open>, close = 0xaaaaaaaa0afc <close_open>, 
      lock = 0xaaaaaaaa0b40 <lock_open>, unlock = 0xaaaaaaaa0b74 <unlock_open>, 
      name = 0xaaaaaaaa1088 "OPEN"}
(gdb) p STATE_CLOSED
$7 = {open = 0xaaaaaaaa09d8 <open_closed>, close = 0xaaaaaaaa0a1c <close_closed>, 
      lock = 0xaaaaaaaa0a50 <lock_closed>, unlock = 0xaaaaaaaa0a94 <unlock_closed>, 
      name = 0xaaaaaaaa1080 "CLOSED"}

(gdb) p open_closed
$8 = {void (Door *)} 0xaaaaaaaa09d8 <open_closed>
(gdb) p close_closed
$9 = {void (Door *)} 0xaaaaaaaa0a1c <close_closed>
(gdb) p open_open
$16 = {void (Door *)} 0xaaaaaaaa0ac8 <open_open>
(gdb) p close_open
$17 = {void (Door *)} 0xaaaaaaaa0afc <close_open>
C++ 复制代码
typedef struct Door Door;           // ① 前向声明,使得后续可以只用 Door 代替 struct Door
typedef void (*op_func_t)(Door*);   // ② 定义函数指针类型:接收 Door*,无返回值
/*
这里 op_func_t 是一个函数指针类型,它接受一个 Door* 类型的参数。如果在此之前没有 Door 类型的任何声明,
编译器会报错:"未知类型名 Door"。

通过前向声明 typedef struct Door Door;,编译器知道 Door 是一个结构体类型(虽然还不完整),
所以可以合法地使用 Door* 作为参数类型。后续再给出 struct Door 的完整定义:

在完整定义出现之前,不能定义 Door 类型的变量(因为编译器不知道大小),也不能访问其成员。
但可以定义指向 Door 的指针,因为指针大小固定。
可以声明以 Door 为参数或返回值的函数(但函数体内仍不能访问成员)
*/


typedef struct DoorState {
    op_func_t open;      // ③ 成员都是函数指针
    op_func_t close;
    op_func_t lock;
    op_func_t unlock;
    const char* name;
} DoorState;

struct Door {
    const DoorState* state;  // ④ 门对象持有一个指向状态结构体的指针
};

//在 C 语言中,extern 关键字用于声明一个变量或函数是在别处定义的(通常在其他源文件或本文件后面)。
//对于全局变量,默认情况下(不带 static)已经有外部链接属性,但加 extern 可以显式声明而不定义。
extern const DoorState STATE_CLOSED;   // ⑤ 声明外部的全局常量状态实例
extern const DoorState STATE_OPEN;
extern const DoorState STATE_LOCKED; 
//这几行是声明,告诉编译器:"STATE_CLOSED 等变量是 const DoorState 类型,
//它们在别处定义(可能在同一个文件后面或其他源文件中),链接时请找到它们。"  

//实际定义出现在后面:
const DoorState STATE_CLOSED = { ... };
const DoorState STATE_OPEN   = { ... };
const DoorState STATE_LOCKED = { ... };
C 复制代码
**const**

初始化时,只要是希望不被修改的值,都需要被定义成 const 类型,防止程序运行时被修改。

原因:
安全性:状态表的内容(函数指针和状态名)应当是 只读的,因为在程序生命周期中不会改变。
       如果某个代码错误地试图修改 STATE_CLOSED.open,加 const 后编译器会报错。
内存优化:const 全局变量通常被放置于只读数据段(.rodata),与可读写的 .data 或 .bss 段分离。
       多个门对象可以共享同一份状态表,避免不必要的内存拷贝。
清晰表达意图:任何阅读代码的人看到 const 就知道这个变量是常量,不会在运行时被修改,有助于理解设计。

哪些数据应该使用const?
全局或静态的配置数据(如状态表、虚函数表、查找表)。
字符串常量(const char*)。
任何不希望被意外修改的数据。

哪些数据不应该用 const?
必须修改的变量(如门对象的 state 指针,
它是运行时变化的,所以struct Door中的state是指向const DoorState的指针,但指针本身不是const)。

**extern**

如果直接写 const DoorState STATE_CLOSED; 而没有 extern,编译器会认为这是一个定义(分配内存),
可能导致重复定义错误(当多个文件包含同一头文件时)。

extern 告诉编译器:"这只是声明,不是定义",避免多重定义。

对于 const 全局变量,C 语言默认具有内部链接(除非显式使用 extern),
所以在头文件中使用 extern const 是常见做法,使得多个源文件可以共享同一个只读常量。

总结:
extern:声明变量存在,不分配内存,链接时解析。
const:限定变量只读,不可修改。
组合 extern const 用于声明一个全局只读变量,定义在其他地方。


**typedef**

struct Door:声明一个名为 Door 的结构体标签
(不完全类型,此时编译器只知道这个类型存在,但不知道其包含哪些成员)。

typedef:将 struct Door 定义为一个新的类型别名 Door(注意这里 Door 既是标签名也是类型名,
但通常标签和类型名可以相同)。

整体效果:定义了一个类型 Door,它代表 struct Door,并且 struct Door 是不完全类型。


typedef struct Door Door;            // 前向声明 + 类型别名
struct Door;                         // 只声明结构体标签(不定义)

如果只用 struct Door; 而没有 typedef,那么使用类型时必须写 struct Door,
而 Door 不是类型名。为了让 Door 作为类型名,typedef 是必要的。

总结:
typedef struct Door Door; 是前向声明,使得 Door 成为一个不完全类型,可以用于指针或函数声明。

它解决了类型相互引用(如 op_func_t 需要 Door*,而 Door 的成员又用了 op_func_t)的循环依赖问题。

完整定义在后面给出,此时类型变为完整。

//------------
extern const 允许将状态表声明与定义分离,使得多个文件可以共享同一个常量状态表,且避免重复定义。

前向声明 typedef struct Door Door; 让函数指针类型 op_func_t 可以顺利使用 Door* 参数,
同时门结构体的定义又可以包含 DoorState*,形成了合理的依赖关系。这正是相互递归数据结构的典型处理方式。
C 复制代码
/*
定义状态表中的函数指针,定义"在当前状态下,收到某个操作时应该做什么":
.open = open_closed;
表示:当门处于关闭状态时,执行"开门"操作,应该调用 open_closed 函数。
.close = close_closed;
表示:当门处于关闭状态时,执行"关门"操作,应该调用 close_closed 函数
*/
const DoorState STATE_LOCKED = {
    .open = open_locked,      *// 将 **open 函数指针**赋值为 **open_locked*
    .close = close_locked,    *// 将 **close 函数指针**赋值为 **close_locked*
    .lock = lock_locked,
    .unlock = unlock_locked,
    .name = "LOCKED"
};
/*
这是 C99 标准的指定初始化器(designated initializer) 语法。
在初始化结构体时,使用 .成员名 = 值 的形式可以显式指定要初始化的成员,而不必按顺序。
其他未指定的成员会被自动初始化为 0(对于指针就是 NULL)。
*/

//等价于:
const DoorState STATE_LOCKED = { open_locked, close_locked, lock_locked, 
                                 unlock_locked, "LOCKED" };

/*
.成员名 的好处:
可读性好,清楚知道每个值赋给哪个成员。
不依赖成员声明的顺序,便于维护。
可以只初始化部分成员,其余自动清零。
*/
  • Door 是门对象结构体,内部仅有一个 state 指针,指向当前所处的 DoorState 常量。

  • DoorState 是状态表类型,包含四个函数指针(open、close、lock、unlock)和一个状态名称字符串。

  • op_func_t 是函数指针类型,定义为 void (*)(Door*),所有状态函数都符合此类型。

  • STATE_CLOSED、STATE_OPEN、STATE_LOCKED 是三个全局常量,属于 DoorState 类型,分别初始化为对应状态下的具体函数(如 open_closed 等)。这些函数在图中用 状态函数 类示意。

  • Door d 是 main 函数中的局部变量 d,其 state 最初指向 &STATE_CLOSED,运行时可能随操作改变指向(如切换到 &STATE_OPEN)。

  • 所有状态函数(如 open_closed、close_open 等)均定义在源文件中,通过函数指针被状态表引用。

C 复制代码
static void open_closed(Door* d) {
    printf("当前状态: %s, 执行 open -> 切换到 OPEN\n", d->state->name);
    d->state = &STATE_OPEN;
}

void door_open(Door* d) { d->state->open(d); }

//-----main-----
Door d;//在栈上创建一个门对象 d,此时里面的 state 指针还未初始化(垃圾值)。
d.state = &STATE_CLOSED;//将 d 的当前状态设置为 "关闭(CLOSED)"
                        //STATE_CLOSED 是一个全局常量状态表,
                        //记录了在关闭状态下各个操作(open/close/lock/unlock)应该执行的函数。

door_open(&d); // CLOSED -> OPEN
               //调用 door_open 函数,它内部执行 d->state->open(d)。
               //因为 d.state 指向 STATE_CLOSED,所以 d->state->open 就是 open_closed 函数
               //(见上面定义)。open_closed 函数的作用是:打印"当前状态: CLOSED, 
               //执行 open -> 切换到 OPEN",然后将门的当前状态改为 &STATE_OPEN(即打开状态)。

//这个过程是:初始关闭状态 -> 执行开门操作 -> 状态变为打开状态。
//-----------------

/*
当调用 door_open(&d) 时,实际上执行的是 d->state->open(d),
即调用当前状态(STATE_CLOSED)的 open 函数指针,也就是 open_closed。
状态切换:d->state = &STATE_OPEN;
*/


door_open(&d);   // OPEN 中再打开: 无效
/*
当再次调用 door_open(&d) 时,实际上执行的是 d->state->open(d),
即调用当前状态(STATE_OPEN)的 open 函数指针,也就是 open_open。
状态不切换
*/

其他代码逻辑同上。
 
1.5.5.3.3 状态模式的应用场景

函数指针在状态模式中的核心作用是将状态与行为解耦,并通过查表(函数指针数组或结构体) 实现运行时动态切换。

传统状态机通常用 switch(状态) 处理不同事件,当状态和事件增多时代码臃肿且难以维护。用函数指针状态表可以:

C++ 复制代码
// 状态表:状态 × 事件 → 处理函数
typedef void (*event_handler_t)(void* ctx);
event_handler_t state_table[STATE_COUNT][EVENT_COUNT] = { ... };

// 调用
state_table[current_state][event](ctx);

优点:新增状态只需添加一行表项和对应处理函数,无需修改分发逻辑。避免冗长的 switch-case / if-else 分支

C++ 复制代码
// 状态表:状态 × 事件 → 处理函数
typedef void (*event_handler_t)(void* ctx);
event_handler_t state_table[STATE_COUNT][EVENT_COUNT] = { ... };

// 调用
state_table[current_state][event](ctx);

如:TCP 协议状态机:CLOSED、LISTEN、SYN_SENT、ESTABLISHED 等状态,每个状态下收到不同报文(SYN、ACK、RST 等)执行不同动作。用函数指针表实现高效、确定性响应。

蜂窝:搜网、注网、建立连接、断开等。

1.5.5.3.4 手机蜂窝网络状态机的一个简化示例

搜网、注网、建立连接、断开。

C++ 复制代码
// cellular_state.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 前向声明
typedef struct CellularContext CellularContext;

// 事件类型
typedef enum {
    EV_SEARCH_START,      // 启动搜网
    EV_NETWORK_FOUND,     // 找到网络
    EV_REGISTER_COMPLETE, // 注册成功
    EV_REGISTER_FAIL,     // 注册失败
    EV_CONNECT_REQ,       // 请求连接
    EV_CONNECT_SUCCESS,   // 连接建立成功
    EV_CONNECT_FAIL,      // 连接失败
    EV_DISCONNECT,        // 主动断开
    EV_LOST_NETWORK,      // 网络丢失
    EV_TIMEOUT            // 超时
} Event;

// 状态机上下文,保存当前状态和一些数据
struct CellularContext {
    int (*state_handler)(CellularContext* ctx, Event ev);
    const char* state_name;
    int retry_count;      // 搜网重试次数
    bool has_network;     // 是否找到网络
};

// 状态处理函数声明
static int handle_searching(CellularContext* ctx, Event ev);
static int handle_registered(CellularContext* ctx, Event ev);
static int handle_connected(CellularContext* ctx, Event ev);
static int handle_idle(CellularContext* ctx, Event ev);

// 定义状态表(函数指针数组)------ 每个状态对应一个处理函数
typedef int (*state_func_t)(CellularContext*, Event);
static state_func_t state_table[] = {
    [0] = handle_idle,
    [1] = handle_searching,
    [2] = handle_registered,
    [3] = handle_connected
};

// 状态枚举(与表索引对应)
enum State {
    ST_IDLE = 0,
    ST_SEARCHING,
    ST_REGISTERED,
    ST_CONNECTED
};

static const char* state_names[] = {
    "IDLE", "SEARCHING", "REGISTERED", "CONNECTED"
};

// 切换状态
static void change_state(CellularContext* ctx, enum State new_state) {
    ctx->state_handler = state_table[new_state];
    ctx->state_name = state_names[new_state];
    printf("[状态转换] 进入 %s\n", ctx->state_name);
}

// 事件分发入口
static void dispatch(CellularContext* ctx, Event ev) {
    printf("事件: %d, 当前状态: %s\n", ev, ctx->state_name);
    int next_state = ctx->state_handler(ctx, ev);
    if (next_state >= 0) {
        change_state(ctx, (enum State)next_state);
    }
}

// ---------- 各状态处理函数 ----------
static int handle_searching(CellularContext* ctx, Event ev) {
    switch (ev) {
        case EV_NETWORK_FOUND:
            printf("搜到网络,尝试注册...\n");
            // 模拟注册过程,此处直接触发注册完成(实际可能异步)
            dispatch(ctx, EV_REGISTER_COMPLETE);
            return -1; // 暂不切换状态,等待事件处理完
        case EV_REGISTER_COMPLETE:
            printf("注网成功!\n");
            return ST_REGISTERED;
        case EV_TIMEOUT:
            ctx->retry_count++;
            if (ctx->retry_count < 3) {
                printf("搜网超时,重试第 %d 次\n", ctx->retry_count);
                return -1; // 保持在 SEARCHING,重新搜网
            } else {
                printf("搜网失败,进入 IDLE\n");
                return ST_IDLE;
            }
        default:
            printf("SEARCHING 状态下忽略事件 %d\n", ev);
            return -1;
    }
}

static int handle_registered(CellularContext* ctx, Event ev) {
    switch (ev) {
        case EV_CONNECT_REQ:
            printf("请求建立连接...\n");
            // 模拟连接建立(直接成功或失败)
            dispatch(ctx, EV_CONNECT_SUCCESS);
            return -1;
        case EV_CONNECT_SUCCESS:
            printf("连接建立成功!\n");
            return ST_CONNECTED;
        case EV_CONNECT_FAIL:
            printf("连接建立失败\n");
            return -1; // 仍保持在 REGISTERED
        case EV_LOST_NETWORK:
            printf("网络丢失,重新搜网\n");
            return ST_SEARCHING;
        case EV_DISCONNECT:
            printf("主动去注册,进入 IDLE\n");
            return ST_IDLE;
        default:
            return -1;
    }
}

static int handle_connected(CellularContext* ctx, Event ev) {
    switch (ev) {
        case EV_DISCONNECT:
            printf("断开连接\n");
            return ST_REGISTERED;
        case EV_LOST_NETWORK:
            printf("连接中网络丢失,重新搜网\n");
            return ST_SEARCHING;
        default:
            printf("CONNECTED 状态下忽略事件 %d\n", ev);
            return -1;
    }
}

static int handle_idle(CellularContext* ctx, Event ev) {
    switch (ev) {
        case EV_SEARCH_START:
            printf("启动搜网\n");
            ctx->retry_count = 0;
            return ST_SEARCHING;
        default:
            printf("IDLE 状态下忽略事件 %d\n", ev);
            return -1;
    }
}

// 主函数演示
int main() {
    CellularContext ctx = {
        .state_handler = state_table[ST_IDLE],
        .state_name = "IDLE",
        .retry_count = 0,
        .has_network = false
    };

    // 模拟一个完整的流程
    printf("\n=== 启动手机 ===\n");
    dispatch(&ctx, EV_SEARCH_START);          // 开始搜网
    dispatch(&ctx, EV_NETWORK_FOUND);         // 找到网络 -> 触发注册完成
    dispatch(&ctx, EV_CONNECT_REQ);           // 请求连接 -> 连接成功
    dispatch(&ctx, EV_DISCONNECT);            // 断开连接 -> 回到 REGISTERED
    dispatch(&ctx, EV_LOST_NETWORK);          // 网络丢失 -> 重新搜网
    dispatch(&ctx, EV_TIMEOUT);               // 搜网超时重试
    dispatch(&ctx, EV_TIMEOUT);               // 再次超时,进入IDLE
    return 0;
}

分析:

状态与事件枚举定义:

C 复制代码
状态枚举 (State):
ST_IDLE = 0
ST_SEARCHING = 1
ST_REGISTERED = 2
ST_CONNECTED = 3

事件枚举 (Event):
EV_SEARCH_START = 0
EV_NETWORK_FOUND = 1
EV_REGISTER_COMPLETE = 2
EV_REGISTER_FAIL = 3
EV_CONNECT_REQ = 4
EV_CONNECT_SUCCESS = 5
EV_CONNECT_FAIL = 6
EV_DISCONNECT = 7
EV_LOST_NETWORK = 8
EV_TIMEOUT = 9

每个(状态,事件)对应的处理函数行为(及返回值/副作用):

状态 ST_IDLE

事件 处理行为 返回新状态
EV_SEARCH_START 打印"启动搜网",重置重试计数 ST_SEARCHING
其他所有事件 打印"忽略事件" 无变化(返回 -1 或原状态)

状态 ST_SEARCHING

事件 处理行为 返回新状态
EV_NETWORK_FOUND 打印"搜到网络,尝试注册...";递归调用dispatch 发送 EV_REGISTER_COMPLETE 暂不返回(实际由内部事件驱动)
EV_REGISTER_COMPLETE 打印"注网成功!" ST_REGISTERED
EV_TIMEOUT 重试计数自增;若 <3 则打印重试次并停留在本状态;否则打印"搜网失败" 重试中返回 -1;超限返回 ST_IDLE
其他事件 打印"忽略事件" -1

状态 ST_REGISTERED

事件 处理行为 返回新状态
EV_CONNECT_REQ 打印"请求建立连接...";递归调用 EV_CONNECT_SUCCESS 暂不返回
EV_CONNECT_SUCCESS 打印"连接建立成功!" ST_CONNECTED
EV_CONNECT_FAIL 打印"连接建立失败" -1
EV_LOST_NETWORK 打印"网络丢失,重新搜网" ST_SEARCHING
EV_DISCONNECT 打印"主动去注册,进入 IDLE" ST_IDLE
其他事件 忽略 -1

状态 ST_CONNECTED

事件 处理行为 返回新状态
EV_DISCONNECT 打印"断开连接" ST_REGISTERED
EV_LOST_NETWORK 打印"连接中网络丢失,重新搜网" ST_SEARCHING
其他事件 忽略 -1

每个 handle_xxx 函数内部执行对应的行为,并可能调用 change_state 来切换上下文中的状态(或返回新状态值由分发器统一处理)。代码中,状态机采用的是每个状态一个处理函数的一维查表方式(state_tablestate 指向该状态的处理函数,函数内部再通过 switch(event) 分派具体行为)。一维表更紧凑,适合事件数量不多的情况。每一个 (状态,事件) 组合都可以对应到原 handle_xxx 函数中的一个 case 分支。

数据结构关系图:

  • CellularContext 是状态机的上下文,保存当前状态处理函数指针(state_handler)、状态名称字符串、重试计数器等。

  • state_func_t 是函数指针类型,定义为 int (*)(CellularContext*, Event),所有状态处理函数都符合此类型。

  • state_table 是一个静态数组,使用指定初始化器将 State 枚举值映射到对应的状态处理函数。例如 ST_SEARCHING = handle_searching。

  • dispatch 函数接收事件,调用当前上下文的 state_handler,并根据返回值(新状态索引)调用 change_state。

  • change_state 根据新状态索引从 state_table 中取出新的处理函数,更新到 CellularContext 的 state_handler 和 state_name。

  • 各个 handle_xxx 函数实现了每个状态下的具体行为,内部通过 dispatch 可以触发"内部事件"(例如注册成功后立即触发连接请求),并返回下一个状态索引或 -1(表示不转换)。

GDB debug:

Java 复制代码
(gdb) p ctx
$2 = {state_handler = 0xaaaaaaaa0c74 <handle_idle>, state_name = 0xaaaaaaaa0dc0 "IDLE",
      retry_count = 0, has_network = false}
  
(gdb) p state_table
$3 = {0xaaaaaaaa0c74 <handle_idle>, 0xaaaaaaaa0a28 <handle_searching>, 
      0xaaaaaaaa0b14 <handle_registered>, 0xaaaaaaaa0c04 <handle_connected>}

(gdb) p handle_idle
$5 = {int (CellularContext *, Event)} 0xaaaaaaaa0c74 <handle_idle>
(gdb) p handle_searching
$6 = {int (CellularContext *, Event)} 0xaaaaaaaa0a28 <handle_searching>
(gdb) p handle_registered
$7 = {int (CellularContext *, Event)} 0xaaaaaaaa0b14 <handle_registered>
(gdb) p handle_connected
$8 = {int (CellularContext *, Event)} 0xaaaaaaaa0c04 <handle_connected>
C 复制代码
//main

printf("\n=== 启动手机 ===\n");

(gdb) p ctx
$2 = {state_handler = 0xaaaaaaaa0c74 <handle_idle>, state_name = 0xaaaaaaaa0dc0 "IDLE",
 retry_count = 0, has_network = false}

dispatch(&ctx, EV_SEARCH_START);          // 开始搜网
C++ 复制代码
// dispatch 事件分发入口
static void dispatch(CellularContext* ctx, Event ev) {
    printf("事件: %d, 当前状态: %s\n", ev, ctx->state_name);
 
/*    
(gdb) p *ctx
$5 = {state_handler = 0xaaaaaaaa0c74 <handle_idle>, state_name = 0xaaaaaaaa0dc0 "IDLE",
      retry_count = 0, has_network = false}
*/
    int next_state = ctx->state_handler(ctx, ev);
    
/*
(gdb) p next_state
$13 = 1
*/    
    if (next_state >= 0) {
        change_state(ctx, (enum State)next_state);
    }
}
C++ 复制代码
//handle_idle
static int handle_idle(CellularContext* ctx, Event ev) {
/*
(gdb) s
handle_idle (ctx=0xffffffffecb0, ev=EV_SEARCH_START) at reg_state.c:139
*/
    switch (ev) {
        case EV_SEARCH_START:
            printf("启动搜网\n");
            ctx->retry_count = 0;
            return ST_SEARCHING;
        default:
            printf("IDLE 状态下忽略事件 %d\n", ev);
            return -1;
    }
}
C++ 复制代码
// change_state 切换状态
static void change_state(CellularContext* ctx, enum State new_state) {
/*
(gdb) p *ctx
$14 = {state_handler = 0xaaaaaaaa0c74 <handle_idle>, state_name = 0xaaaaaaaa0dc0 "IDLE",
 retry_count = 0, has_network = false}
*/
    ctx->state_handler = state_table[new_state];
/*
(gdb) p *ctx
$15 = {state_handler = 0xaaaaaaaa0a28 <handle_searching>, 
state_name = 0xaaaaaaaa0dc0 "IDLE", retry_count = 0, has_network = false}
*/    
    ctx->state_name = state_names[new_state];
/*
(gdb) p *ctx
$16 = {state_handler = 0xaaaaaaaa0a28 <handle_searching>, 
state_name = 0xaaaaaaaa0dc8 "SEARCHING", retry_count = 0, has_network = false}
*/
    printf("[状态转换] 进入 %s\n", ctx->state_name);
}
C 复制代码
状态表 + 函数指针驱动状态机的核心架构:

上下文保存当前状态函数,
事件分发时通过函数指针间接调用,
状态切换则重新绑定函数指针。
相关推荐
武子康1 小时前
调查研究-201 Rust 里的 dev build 和 release build:为什么同一份代码性能差这么多?
后端·架构·rust
raindesound1 小时前
计算机基础:ADT(Abstract Data Type)抽象数据类型 (1)
架构
夕阳与风馨1 小时前
大文件(20GB+)SFTP 下载模块设计与实现
后端·架构
阳光是sunny12 小时前
Vue 项目怎么做用户行为全链路监控?轻量插件方案详解
前端·面试·架构
EMA18 小时前
Docker虚拟化失败解决方案
架构
李斯维18 小时前
从历史的角度看 Android 软件架构
android·架构·android jetpack
JouYY21 小时前
聊一下多 Agent 编排架构的应用实践
架构·llm·agent
Sunia21 小时前
《AgentX 专栏》10-生产部署:3台2C4G云服务器把企业级Agent真正跑起来的完整方案
java·架构
ZhengEnCi2 天前
Q01-高并发点赞系统架构设计
架构