c - 在 C 中实现基本的 vtable
问题描述
我正在尝试在 C 中模拟 vtable 的最基本(玩具)案例。这是一个基本示例:
typedef struct Person {
int id;
char *name;
} Person;
假设我们添加了一种方法(即函数指针):
typedef struct Person {
int id;
char *name;
void (*print_name)(Person);
} Person;
现在我们将初始化它并用它填充片段(让我们忽略内存泄漏):
#include <stdio.h>
#include <stdlib.h>
typedef struct Person Person;
typedef struct Person {
int id;
char *name;
void (*print)(Person *self);
} Person;
void print_name(Person *person) {
printf("Hello %s\n", person->name);
}
Person *init_person(void) {
Person *person = malloc(sizeof(Person));
person->print = print_name;
}
int main(void) {
Person *p = init_person();
p->name = "Greg";
p->print(p);
return 0;
}
在这里运行代码。
如果我要从 中提取函数Person
并将其放入 a 中Person_VTable
,例如:
typedef struct Person {
int id;
char *name;
Person_VTable *vtable;
} Person;
typedef struct Person_VTable {
???
} Person_VTable;
(1) 创建 vtable 和 (2) 用新的 vtable 初始化 Person 对象的正确方法是什么?请注意,我知道这是一个完全微不足道的示例,它可以以更好的方式完成,但我正在了解如何使用我正在工作的主要对象的外部“vtable”来完成它。
此外,这是否也意味着如果我有一个 vtable,而不是只有一个“自我”来引用它来自struct
自身的对象,例如:
void (*print)(Person *self);
我需要有两个间接,所以我知道对象和 vtable 位置?就像是:
void (*print)(Person *self_obj, Person_VTable *self_vt);
如果是这样,那是很多开销!
解决方案
一个基本的 vtable 只不过是一个包含函数指针的普通结构,可以在对象实例之间共享。有两种基本方法可以实现它们。一种是使 vtable 指针成为普通的结构成员(这就是它在 C++ 中的工作原理):
#include <stdio.h>
#include <stdlib.h>
typedef struct Person Person;
typedef struct Person_VTable Person_VTable;
struct Person {
int id;
char *name;
const Person_VTable *vtable;
};
struct Person_VTable {
void (*print)(Person *self);
};
void print_name(Person *person) {
printf("Hello %s\n", person->name);
}
static const Person_VTable vtable_Person = {
.print = print_name
};
Person *init_person(void) {
Person *person = malloc(sizeof(Person));
person->vtable = &vtable_Person;
return person;
}
int main(void) {
Person *p = init_person();
p->name = "Greg";
p->vtable->print(p);
return 0;
}
另一种是使用胖指针(这就是它在 Rust 中的实现方式):
#include <stdio.h>
#include <stdlib.h>
typedef struct Person Person;
typedef struct Person_VTable Person_VTable;
typedef struct Person_Ptr {
Person *self;
const Person_VTable *vtable;
} Person_Ptr;
struct Person {
int id;
char *name;
const Person_VTable *vtable;
};
struct Person_VTable {
void (*print)(Person_Ptr self);
};
void print_name(Person_Ptr person) {
printf("Hello %s\n", person.self->name);
}
static const Person_VTable vtable_Person = {
.print = print_name
};
Person_Ptr init_person(void) {
Person_Ptr person;
person.self = malloc(sizeof(Person));
person.vtable = &vtable_Person;
return person;
}
int main(void) {
Person_Ptr p = init_person();
p.self->name = "Greg";
p.vtable->print(p);
return 0;
}
在 C 中,首选方式是前者,但这主要是出于语法原因:在函数之间按值传递结构没有广泛认可的 ABI,而传递两个单独的指针在语法上相当笨拙。当将 vtable 附加到内存布局不受您控制的对象时,另一种方法很有用。
本质上,vtables 相对于普通函数指针成员的唯一优势是它们节省内存(结构的每个实例只需要携带一个 vtable 指针)并防止内存损坏(vtables 本身可以驻留在只读内存中)。
推荐阅读
- android - 如何广播 Receiver 和 MVVM?
- cryptography - 在 python 3.6 中使用 pycryptodome 或密码学。如何做到这一点?
- java - ANTLR 语法返回可以使用 java 编译器执行的 java 方法名称
- c# - 未找到使用 dotnet Core Web MVC 应用的 Azure AD 身份验证的质询和身份验证方案
- reactjs - 在 React SSR 上使用 redux-thunk 操作必须是普通对象错误
- python - 在 for 循环中计算“Digram”又名核苷酸对
- go - 我可以从嵌套模板访问顶级模板变量吗?
- python - 想要为 sprite pygame 添加一条线索
- java - 为什么 jvm 命令 `jinfo` 在 alpine openjdk8 发行版中没有`-flags`?
- python - 盒子宽度翻倍