目录
1.2 定义一个全局(非 static )的函数指针或提供一个返回该函数指针的接口
背景:在复刻一个关于锂电池(5-16串)BMS电池管理系统的项目时,我想要利用XShell(通过串口通信)实时打印电池各项参数,但对应的 InfoPrint() 函数是 static,但我又不想破坏其静态属性,故选择间接调用static函数的方法。具体技巧如下:
在 C 语言中,被 static 修饰的函数具有内部链接,仅在定义它的源文件中可见。也就是说,不能在其他文件中通过函数名直接调用它。为实现间接调用,可以在定义 static 函数的文件中暴露一个函数指针或封装接口,并在需要调用的文件中通过该接口/指针进行调用。
1. 步骤
1.1 定义 static 函数
static void hidden_func(void) { /* ... 函数实现 ... */ }
1.2 定义一个全局(非 static )的函数指针或提供一个返回该函数指针的接口
可以在此文件中声明一个函数指针并在初始化函数中赋值:
void (*hidden_ptr)(void) = NULL;
void init_hidden(void) {
hidden_ptr = hidden_func; // 指针指向静态函数
}
或者提供一个 getter 函数返回指向 static 函数的指针:
typedef void (*FuncPtr)(void);
FuncPtr get_hidden_func(void) {
return hidden_func;
}
这样做的原理是:虽然 hidden_func本身不能被链接器导出,但在同一文件内可以获取它的地址并赋值给全局指针或返回出去。这类似于注册回调的做法:在静态函数所在模块中,把静态函数的指针登记到一个可访问的位置。
1.3 定义普通(非 static )接口函数
该函数在内部调用静态函数,使其可被外部调用。例如:
static void hidden_func(void) { /* ... */ }
void call_hidden(void) {
hidden_func(); // 在接口函数中调用静态函数
}
这样外部文件只需调用 call_hidden() 即可间接执行 hidden_func()
1.4 在其他文件中通过接口或回调调用
在需要调用 static 函数的文件中,通过 extern 引用前面定义的指针或接口,然后调用:
1.4.1 采用全局函数指针方案
在其他文件中声明并初始化:
extern void (*hidden_ptr)(void);
extern void init_hidden(void);
int main(void) {
init_hidden(); // 初始化,使指针指向 static 函数
hidden_ptr(); // 通过指针调用
return 0;
}
1.4.2 使用 getter 函数
只需调用该函数获得指针:
extern FuncPtr get_hidden_func(void);
int main(void) {
FuncPtr f = get_hidden_func();
f(); // 间接调用
return 0;
}
1.4.3 采用接口封装(wrapper)方案
直接调用在静态文件中定义的普通接口函数:
extern void call_hidden(void);
int main(void) {
call_hidden(); // 调用接口函数,在内部执行 static 函数
return 0;
}
无论哪种方式,关键都是通过指针转发或调用中间接口实现:先在静态函数所在文件把函数地址保存到可导出的指针或容器里,再由其他文件调用该指针/接口。
2. 示例
2.1 最小示例
演示 static 函数通过函数指针被间接调用:
/* module.c:定义静态函数并暴露接口 */
#include <stdio.h>
/* 内部静态函数,文件内可见 */
static void hidden(void) {
printf("Hidden static function\n");
}
/* 全局函数指针和初始化函数 */
void (*hidden_ptr)(void) = NULL; // 声明指针
void init_hidden(void) {
hidden_ptr = hidden; // 指针指向静态函数
}
/* module.h */
extern void (*hidden_ptr)(void);
void init_hidden(void);
/* main.c:通过接口调用隐藏的 static 函数 */
#include "module.h"
int main(void) {
init_hidden(); // 将指针赋值为静态函数的地址
hidden_ptr(); // 通过指针调用静态函数
return 0;
}
以上代码中,hidden() 被声明为 static,只能在 module.c 中被直接访问。但我们在同一文件内将其地址赋给了全局指针 hidden_ptr。在 main.c 中先调用 init_hidden() 初始化指针,然后通过 hidden_ptr() 间接调用该静态函数。
2.2 完整示例
在 module.c 中定义多个 static 计算函数(带两个参数),并通过一个结构体数组来注册它们。外部模块只拿到结构体中的函数指针和名称,就能调用这些本该“私有”的 static 函数。
/* module.h */
#ifndef MODULE_H
#define MODULE_H
#include <stddef.h>
/* 函数签名:两个 int 参数,返回 int */
typedef int (*ComputeFunc)(int a, int b);
/* 注册表项:名称 + 函数指针 */
typedef struct {
const char *name;
ComputeFunc func;
} ComputeEntry;
/* 外部可见的注册表和大小 */
extern ComputeEntry compute_table[];
extern size_t compute_table_size;
/* 初始化函数:在程序启动时调用,将 static 函数地址填入表中 */
void init_compute_table(void);
#endif /* MODULE_H */
/* module.c */
#include "module.h"
/* —— 私有静态函数 —— */
static int add(int a, int b) {
return a + b;
}
static int multiply(int a, int b) {
return a * b;
}
static int subtract(int a, int b) {
return a - b;
}
/* —— 定义一个不带函数指针的“空壳”表 —— */
ComputeEntry compute_table[] = {
{ "add", NULL },
{ "multiply", NULL },
{ "subtract", NULL }
};
/* 表的大小 */
size_t compute_table_size = sizeof(compute_table) / sizeof(compute_table[0]);
/* 初始化:把静态函数的地址写进去 */
void init_compute_table(void) {
compute_table[0].func = add;
compute_table[1].func = multiply;
compute_table[2].func = subtract;
}
/* main.c */
#include <stdio.h>
#include "module.h"
int main(void) {
/* 第一步:初始化,让指针指向 static 函数 */
init_compute_table();
/* 第二步:遍历表,调用各个函数 */
for (size_t i = 0; i < compute_table_size; ++i) {
ComputeEntry *e = &compute_table[i];
if (e->func) {
int x = 10, y = 3;
int result = e->func(x, y);
printf("%s(%d, %d) = %d\n", e->name, x, y, result);
}
}
return 0;
}
输出如下:
add(10, 3) = 13
multiply(10, 3) = 30
subtract(10, 3) = 7
注:
-
封装性:
add、multiply、subtract都是static,只能在module.c内部访问。 -
注册表:
compute_table数组对外可见,但最初其func指针全为NULL,调用者没法直接拿到静态函数地址。 -
初始化:
init_compute_table()在文件内部把每个hidden函数地址写入表里,之后外部模块就能通过结构体里的指针来间接调用它们。 -
多参数与结构体:示例中函数都带了两个参数,还配合结构体,把名称和指针捆绑,易于扩展(比如动态插件、命令解析、脚本绑定等场景)。
1万+

被折叠的 条评论
为什么被折叠?



