我正在开发一个VM,我希望能够调用已编译的函数。但是,因为每个函数可能最终都有不同的签名,我的计划是将所有调用一般化为2种可能的场景 - 调用函数而不返回且没有参数,以及调用函数需要一个void *
参数。
计划与thiscall
类似地使用它 - 所有参数都在传递指针的位置正确对齐,并通过间接检索参数。不应该比从堆栈中读取它们慢,至少是IMO。
所以而不是:
int foo(int a, int b) { return a+b; }
我可以有类似的东西:
void foo2(void *p) {
*(int*)p = *(int*)(p + 4) + *(int*)(p + 8);
}
所以我的问题是使用这种方法可能会出现什么问题?我可以立刻知道它是“在黑暗中”工作,因此正确计算偏移量至关重要。这也有点不方便,因为所有临时工都需要由用户提供。假设我的VM编译器将处理这两个问题,我最关心的是性能 - 我不想创建一个普通的函数,并且对于每个普通函数一个void *
包装器 - 我想直接使用该约定所有函数,所以我不禁想知道编译器在编译代码中使用函数时编译函数有多好?是否会有任何其他可能的性能影响我忽略(排除__fastcall
将使用一个寄存器和一个更少的间接)?
答案 0 :(得分:0)
性能方面(和易用性)你可能最好用cdecl
- 一切都进入堆栈。 C标准允许您使用任意参数
typedef void (__cdecl * function_with_any_parameters)();
您必须确保将您要调用的所有函数定义为:
void __cdecl f(type1 arg1, type2 arg2, type3 arg3); // any amount of arguments
只需使用适量的参数调用它们:
f(arg1, arg2, arg3, arg4);
如果你希望通过单个指针,那么你会有额外的开销:一个指针。最简单的方法是将所有函数定义为接受指向匿名结构的指针:
void f(struct {type1 a; type2 b;} * args);
然后,您可以使用指向相应结构的指针调用该函数,以避免任何错位。
struct {type1 a; type2 b;} args = {arg1, arg2};
f(&args);
您正在有效地实施cdecl
。
答案 1 :(得分:0)
在运行一些基准测试后,我会说编译器在优化类似的指针函数方面做得非常好。 void *
函数与add
函数和常规+
运算符一样快。
似乎这个约定对于提供必要的调用抽象非常有用,而不会损害优化和整体性能。唯一的牺牲是安全,根据应用背景,这可能是也可能不是主要关注点。