问题描述
我正在尝试为支持动态调度和简单继承的面向对象语言制作一个玩具编译器。这意味着只要声明了 Parent 类,就可以使用扩展父类的任何类 Child。它继承了它的所有字段和方法,并且可以覆盖方法。
到目前为止,我一直在考虑实现虚函数表,并确保父类和子类的内存布局尽可能相似。下面是一个 C 语言的小例子来说明我的意思:
#include <stdlib.h>
typedef struct {
struct ParentVTable *_vtable;
int inheritedField;
} Parent;
struct ParentVTable {
void (*inheritedMethod)(Parent *,int);
};
void Parent_inheritedMethod(Parent *self,int b) {
b = 0;
}
struct ParentVTable ParentVTable_inst = {
.inheritedMethod = &Parent_inheritedMethod
};
void Parent_init(Parent *self) {
self->_vtable = &ParentVTable_inst;
self->inheritedField = 42;
}
Parent *Parent_new(void) {
Parent *self = (Parent*)malloc(sizeof(Parent));
Parent_init(self);
return self;
}
typedef struct {
struct ChildVTable *_vtable;
int inheritedField;
int newField;
} Child;
struct ChildVTable {
void (*inheritedMethod)(Child *,int);
int (*newMethod)(Child *,int);
};
int Child_newMethod(Child *self,int i) {
return i + self->inheritedField;
}
struct ChildVTable ChildVTable_inst = {
.inheritedMethod = (void (*)(Child *,int)) Parent_inheritedMethod,.newMethod = &Child_newMethod
};
void Child_init(Child *self) {
Parent_init((Parent *) self);
self->_vtable = &ChildVTable_inst;
self->newField = 0;
}
Child *Child_new(void) {
Child *self = (Child*)malloc(sizeof(Child));
Child_init(self);
return self;
}
int main() {
Parent *p = (Parent*) Child_new();
return 0;
}
正如您在 main 方法中看到的那样,只要将 Child 类强制转换作为父类,就可以将其用作父类。使用 clang 此转换在 LLVM 中转换为 bitcast 操作,否则 llvm 无效(llvm 代码无法编译)。这是对应的一段代码:
define i32 @main() #0 {
%1 = alloca i32,align 4
%2 = alloca %struct.Parent*,align 8
store i32 0,i32* %1,align 4
%3 = call %struct.Child* @Child_new()
%4 = bitcast %struct.Child* %3 to %struct.Parent*
store %struct.Parent* %4,%struct.Parent** %2,align 8
ret i32 0
}
到那里一切都很好。问题是,在我试图创建的语言中,以及在大多数实现继承的面向对象语言中,在 C 中完成的这种强制转换是隐式的。我无法看到,在编译时,需要强制转换。因此,我的问题是我如何知道何时必须对 llvm 变量进行位转换,例如在分配它之前或在将其作为参数传递给 call 等之前。假设这仅在运行时知道。我是否遗漏了什么,我是否应该每次都简单地进行比特转换以确保在代码中传递正确的“对象”?任何帮助将不胜感激!
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)