是否可以显式调用名称混淆函数?

38

假设我有类似以下的东西

struct Foo {
    void goo() {printf("Test");}
}

external void _ZN3Foo3gooEv(Foo *f);

int main() {
        Foo f;
        _ZN3Foo3gooEv(&f);
}

能否通过函数的名称捆绑版本在此处调用Foo :: goo()?

编辑:

澄清一下,这只是一个实验,看看是否可以显式调用名称混淆的函数。这里没有进一步的目标。

我认为所有成员函数基本上将this指针作为它们的第一个参数。

我知道这不会链接,但我不明白为什么。我认为名称混淆发生在编译时,当链接器运行时,它会解析对名称混淆的函数的调用。(这就是为什么我想如果我们将_ZN3Foo3gooEv留作外部,它将转到符号表以查找它)。

我在这里误解了什么吗?


8
这听起来可能是XY问题的一个例子。您能否分享更多关于您这样做的最终目标的细节? - nanofarad
4
我怀疑这是不可能的,因为C++编译器会将 "_ZN3Foo3gooEv" 编码成其他东西,然后就找不到匹配项了。 - Mooing Duck
1
无论如何你都不能正确地调用它,因为没有实例,所以即使它链接了(它不会),它也永远不会起作用。这显然是一个 XY 问题。 - user207421
12
我在做这件事情时没有一个最终目标,这只是为了展示知识而已。您能否详细说明一下不能在没有实例的情况下调用 goo 是什么意思?难道 goo 不是在内部转换成一个基本上接受 Foo 对象指针(即 this 指针)的某个函数吗?我假设 f 会在这里充当我们的实例。 - Henry
1
@MarquisofLorne 还有 std::invoke,可以让您将实例作为第一个参数传递,特别是用于像这样的成员函数指针的工作,还有奇怪的成员函数指针语法 (object).*(ptrToMember) - Mooing Duck
显示剩余4条评论
1个回答

37
你可以这样做,但有一些注意事项。
你要么以会生成代码的方式使用成员函数,要么将其设置为非内联函数,并且你的重载定义应该是"extern "C"",以防止"双重符号重整"。例如:
#include <cstdio>

struct Foo {
    const char* message;
    void goo();
};

void Foo::goo() {
    std::printf("%s", this->message);
}

extern "C" void _ZN3Foo3gooEv(Foo *f);

int main() {
        Foo f{ "Test" };
        _ZN3Foo3gooEv(&f);
}

在gcc中,会很好地工作并保持稳定。

这是因为大多数系统上成员函数的调用约定等效于自由函数的默认调用约定。将this传递给成员函数,就好像它是第一个参数一样,显式参数占据后面的arg-passing插槽。(寄存器和/或堆栈)。我认为至少对于x86-64、ARM 32位和64位以及32位x86其他操作系统都是如此。

clang似乎特别支持这种用法: 当gcc假装_ZN3Foo3gooEvFoo::goo是两个不同的实体(因此无法替换和内联)时,它将Foo::goo内联到main中。

在MSVC中,你可以做类似的事情。然而,在Windows上的x86-32代码中,调用约定__thiscall被使用,其中this指针不作为第一个参数传递,而是在ECX寄存器中传递,并将其他参数放在堆栈上。如果使用clang或gcc交叉编译x86-32,则可以使用[[gnu::thiscall]](__attribute__((thiscall)))。(fastcall类似,如果只有一个arg,则使用2个arg将第一个2个寄存器而不是仅仅第一个)。


但实际上没有必要这样做。它只能被视为编译器扩展(因为它使用了_Capital_符号),如果你需要一种从C中调用这些函数的方法,可以在C++翻译单元中定义一个帮助函数void Foo_goo(struct Foo*)。它也可以调用私有成员函数,但是你已经可以通过模板特化以符合标准的方式来实现这一点。

10
当你以编译器为目标时,不存在所谓的未定义行为(UB)。 - Passer By
3
好的,这是UB(未定义行为),因此它并不是有效的C++代码,你永远不应该这样做,但在这些实现中,它“可以”正常工作。this指针的值已经被设置为f,一个非虚函数在概念上和实现上都可以看作是一个带有额外参数this的自由函数。这里是在Clang和GCC上不会失败的示例 - HTNW
14
@MarquisofLorne UB是一个标准构造。编译器是一种软件。一段软件具有其自身的行为,声称其是“未定义”的是绝对错误的。 - Passer By
6
@Artyer — external "C"并不会禁止名称修饰,它只是告诉编译器按照C编译器的方式进行名称修饰。这通常意味着模拟加入一个下划线,因此声明可能需要省略前面的下划线。 - Pete Becker
3
@PasserBy - 行为未定义。这意味着仅语言定义无法告诉您程序的操作。这并不意味着一定会发生糟糕的事情。 - Pete Becker
显示剩余14条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接