在C中模拟thiscall以实现struct函数而无需自引用

时间:2011-12-22 21:16:54

标签: c oop assembly compiler-construction

这与一系列面向对象的C贴子有关,但不同之处在于我不想要所有的功能,只有一个:

能够做到这一点:

struct foo { 
  int (*bar)(void);
  char baz;
};

然后有一个反向引用。 C有一个名为cdecl的调用约定;基本上将参数推送到堆栈,有一个返回指针,然后跳转到某个地址。该地址的代码从堆栈中弹出参数并继续它的快乐方式。

这个调用约定略有不同,因为它增加了一个额外的参数,一个"这个"指针,含蓄地。

因为你可以很容易地在C中开始执行任意字节代码而且因为gcc支持内联汇编程序模板,所以这听起来像你可以制作一些宏,这样你就可以做类似的事情:

int bar(void) {
  GETTHIS;
  cThis->baz = 0;
}

int createFoo(struct Foo*pFoo) {
  ASSIGN(pFoo, bar);
} 

基本上,ASSIGN会做的是回避cdecl来模仿一个这样的样式约定,然后GETTHIS做的就是会计技巧的另一面。

我想知道是否:

  • 存在此解决方案
  • 如果没有,是否有原因无法完成

仅此一点;真实的方便"我们所有人都同意这里的成年人"样式成员函数,简直太棒了。谢谢!

注意:

  • 我只是在这里谈论x86,linux,gcc ......我知道世界是一个广阔而奇怪的地方。
  • 出于纯粹的好奇心

3 个答案:

答案 0 :(得分:4)

第一次小修正:

  

C有一个名为cdecl的调用约定;基本上推动了争论   到堆栈,有一个返回指针,然后跳转到某个地址。该   该地址的代码将参数从堆栈中弹出并继续运行   快乐的方式。

被叫方弹出堆栈中的参数。它会读取参数而不会从堆栈中弹出它们。而是由调用者清除堆栈。这是因为在cdecl约定中,被调用者不知道参数的确切数量。

现在,关于你的问题。没有严格定义的 thiscall 调用约定。更多信息here

GCC-具体:

  

thiscall几乎与cdecl相同:调用函数清除   堆栈,参数以从右到左的顺序传递。该   差异是添加了这个指针,它被推到了上面   堆栈最后,好像它是函数中的第一个参数   原型

你无法隐藏额外的this参数并在函数体内检索它,特别是因为this第一个函数参数。如果隐藏它 - 所有其他功能参数将被移位。相反,你可以(并且应该恕我直言)将它声明为第一个函数参数,并且你可以直接访问它,而不需要额外的技巧:

int bar(struct foo *cThis) {
  cThis->baz = 0;
}

MSVC特异性:

  

...这个指针在ECX中传递,它是被清理的被调用者   堆栈,镜像C中使用的stdcall约定   编译器和Windows API函数。当函数使用变量时   参数的数量,它是清理堆栈的调用者(参见   的cdecl)。

在这里,您可以通过将ECX寄存器的值复制到本地变量来实现。像这样:

#define GETTHIS \
struct foo *cThis; \
_asm { \
    mov cThis, ecx \
};

但这很棘手,可能并不总是有效。原因是根据thiscall / stdcall调用约定,ECX寄存器保留用于函数,因此编译器可能生成覆盖{{1的值的代码注册。在您调用ECX宏之前,甚至可能会发生这种情况。

答案 1 :(得分:4)

我的朋友最终得到了它:https://gist.github.com/1516195 ...需要指定功能arity并且仅限于x86_64 ......但是,这是一个非常好的妥协,使事情真的不显眼。

答案 2 :(得分:3)

不,C中没有对“thiscall”的支持,只适用于C ++。

这里也没有秘密技巧,只是一些语法糖。

写作时

class foo {
public:
    int bar();
    char baz;
 }; 

编译器在概念上将其重写为

struct foo {
    char baz;
 }; 

 int bar(struct foo* this);

然后当你做

foo   F;
F.bar();

这被编译为等同于

struct foo   F;
bar(&F);

这就是它的全部!

如果您想与Linus一起玩,您只需要手动执行此操作,因为不信任您在输入g ++而不是gcc时不使用虚拟继承。< / p>