如何降低C语言结构体中函数指针的内存占用?
解决C语言模拟OOP时函数指针内存冗余问题
通用解决方案
核心思路是把所有实例共享的函数指针集合抽离出来,单独形成一个虚表(vtable),每个实例只持有一个指向该虚表的指针,而非每个实例都存储所有函数指针。这样不管新增多少个函数,每个实例仅多占用一个指针的内存,内存消耗不会随函数数量增加而线性增长。
面向对象语言的处理方式
像C++、Java这类OOP语言,本质上都依赖虚表机制解决这个问题:
- 编译器会为包含虚函数的类自动生成一个虚表,用来存储该类所有虚函数的指针。
- 每个类的实例对象中会隐含一个指向虚表的指针(通常称为vptr),同一类的所有实例共享同一个虚表。
- 调用虚函数时,程序通过实例的vptr找到对应虚表,再从中取出函数指针执行。
C语言中的实现方式
1. 定义虚表结构体
先把所有共享的函数指针封装到一个单独的结构体中,也就是虚表:
// Foo的虚表结构体,存储所有共享的函数指针 typedef struct FooVTable { void (*func1)(void*); void (*func2)(void*, int); // 可根据需求添加更多函数指针 } FooVTable;
2. 定义实例结构体
实例结构体只保留指向虚表的指针,以及实例自身的成员变量:
// Foo的实例结构体 typedef struct Foo { const FooVTable* vtable; // 指向共享的虚表 // 实例专属的成员变量 int data; } Foo;
3. 实现虚表中的函数
编写函数的具体逻辑,第一个参数通常是实例指针(对应OOP中的this指针):
#include <stdio.h> void Foo_func1(void* self) { Foo* foo = (Foo*)self; printf("Foo_func1 被调用,data值:%d\n", foo->data); } void Foo_func2(void* self, int value) { Foo* foo = (Foo*)self; foo->data = value; printf("Foo_func2 被调用,data已设为:%d\n", foo->data); }
4. 创建全局共享的虚表实例
因为所有Foo实例共享同一个虚表,所以定义一个全局常量虚表即可:
// 全局共享的Foo虚表 const FooVTable Foo_vtable = { .func1 = Foo_func1, .func2 = Foo_func2 };
5. 初始化实例
创建Foo实例时,将其vtable指针指向全局虚表,并初始化成员变量:
void Foo_init(Foo* self) { self->vtable = &Foo_vtable; self->data = 0; // 初始化成员变量 }
6. 调用实例的函数
通过实例的vtable指针找到对应函数并调用:
int main() { Foo foo1, foo2; Foo_init(&foo1); Foo_init(&foo2); foo1.vtable->func1(&foo1); foo2.vtable->func2(&foo2, 100); foo1.vtable->func2(&foo1, 200); foo1.vtable->func1(&foo1); return 0; }
扩展:实现继承
如果需要支持继承,只需定义子类的虚表(继承父类虚表并覆盖或新增函数),子类实例的vtable指针指向子类虚表即可:
#include <string.h> // 子类Bar的虚表结构体,继承父类虚表并扩展 typedef struct BarVTable { FooVTable base; // 继承父类的虚表 void (*func3)(void*); // 子类新增函数 } BarVTable; // Bar的实例结构体,继承父类实例成员 typedef struct Bar { Foo base; // 继承父类的成员 // 子类专属成员变量 char name[20]; } Bar; // 子类函数实现 void Bar_func3(void* self) { Bar* bar = (Bar*)self; printf("Bar_func3 被调用,name值:%s\n", bar->name); } // 子类的全局虚表 const BarVTable Bar_vtable = { .base = Foo_vtable, // 继承父类的函数 .func3 = Bar_func3 }; // 子类初始化函数 void Bar_init(Bar* self, const char* name) { Foo_init((Foo*)self); // 先初始化父类 self->base.vtable = (const FooVTable*)&Bar_vtable; // 替换为子类虚表 strcpy(self->name, name); }
内容的提问来源于stack exchange,提问作者Sun of A beach
相关产品推荐
相关产品推荐

