【问题标题】:emulating thiscall in C to achieve struct functions without self-referencing在 C 中模拟 thiscall 以实现没有自引用的结构函数
【发布时间】:2011-12-22 21:16:54
【问题描述】:

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

这样做的能力:

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

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

thiscall 约定略有不同,它隐式添加了一个额外的参数,即“this”指针。

由于您可以很容易地开始在 C 中执行任意字节代码,并且由于 gcc 支持内联汇编器模板,因此您可以创建一些宏,以便您可以执行以下操作:

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

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

基本上,ASSIGN 所做的是以某种方式回避 cdecl 以模拟 thiscall 样式约定,然后 GETTHIS 所做的是会计技巧的另一面。

我想知道是否:

  • 存在解决方案,并且
  • 如果没有,如果有原因导致无法完成

仅此而已;真正的“我们在这里都是成年人”风格的成员功能的便利性,简直太棒了。谢谢!

注意事项:

  • 我这里只说 x86、linux、gcc……我知道世界是一个广阔而陌生的地方。
  • 纯粹出于好奇

【问题讨论】:

  • 通常你只需要声明函数接受一个不透明(空指针)的第一个参数,当传递一个函数指针到某个库/对象以便它回调你时,你传递一个指向 一些东西在你身边;当对象调用你的函数时,它会传递这个指针,你的函数会识别 something (在从 void 指针转换之后)。
  • 为什么要这个?如果您需要一种具有成员函数但没有虚函数、异常等开销的类 C 语言,您可以只使用 C++ 并禁止使用慢速特性和 STL。这在嵌入式世界中很常见。
  • @Crashworks,解除 Torvalds 自传的标题,“只是为了好玩”

标签: c oop assembly compiler-construction


【解决方案1】:

我的朋友最终明白了:https://gist.github.com/1516195 ... 需要指定函数 arity 并且仅限于 x86_64 ... 但是是的,这是一个很好的折衷方案,让事情变得非常不引人注目。

【讨论】:

  • 天哪,运行时程序集干预看起来就像你在编写病毒代码!毫无疑问,这是一个非常酷的想法,但我永远不会仅仅为了一些语法糖而放弃这么多的可移植性。 :)
【解决方案2】:

第一个小修正:

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

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

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

Gcc 特有的:

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

您将无法通过隐藏额外的this 参数并在函数体中检索它来达到目的,特别是因为this第一个 函数参数。如果你隐藏它 - 所有其他函数参数都将被转移。相反,您可以(并且应该恕我直言)将其声明为第一个函数参数,并且您可以直接访问它,而无需额外的技巧:

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

Msvc 特定:

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

在这里,您可以通过将ECX 寄存器的值复制到您的局部变量中来达到目的。像这样的:

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

然而,这很棘手,可能并不总是有效。这是因为根据thiscall/stdcall 调用约定,ECX 寄存器是为函数使用而保留的,因此编译器可能会生成覆盖ECX 寄存器值的代码。这甚至可能在您调用 GETTHIS 宏之前发生。

【讨论】:

  • 第一个参数约定是我想要优雅地解决的问题。我完全可以转移参数然后有一个宏以某种方式将它们转移回来...... msvc 风格的解决方案看起来确实很冒险。在 C 中隐藏的地方太多了,我想认为(图书馆的)用户不必以不同的方式做事是可能的。我考虑过定制蹦床……但还没有什么可展示的。
  • 我想了很多关于这个...我认为你是对的...真可惜...让我想多一点。
【解决方案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 时立即使用虚拟继承。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-16
    • 1970-01-01
    • 1970-01-01
    • 2012-10-20
    • 2012-04-18
    • 2012-02-27
    • 2010-11-12
    相关资源
    最近更新 更多