是否可以显式调用名称损坏的函数?

Hen*_*nry 37 c++ name-mangling function-call

假设我有一些类似的东西

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

external void _ZN3Foo3gooEv(Foo *f);

int main() {
        Foo f;
        _ZN3Foo3gooEv(&f);
}
Run Code Online (Sandbox Code Playgroud)

是否可以在这里通过函数的名称修改版本调用 Foo::goo() ?

编辑:

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

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

我知道这不会链接,但我不明白为什么。我认为名称修改发生在编译时,当链接器运行时,它会解析对名称修改函数的调用。(这就是为什么我想如果我们将 _ZN3Foo3gooEv 保留为 extern,它会去符号表中查找)。

我在这里误解了什么吗?

Art*_*yer 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);
}
Run Code Online (Sandbox Code Playgroud)

将正常工作并且在 gcc 中特别稳定。

这是有效的,因为成员函数的调用约定等同于大多数系统上自由函数的默认调用约定。 this被传递给成员函数,就好像它是第一个参数一样,显式参数采用后面的 arg-passing 槽。(寄存器和/或堆栈)。我相信这对于 x86-64,至少 ARM 32 位和 64 位以及 Windows 以外的 32 位 x86 都是正确的。

铛似乎特别支持这种使用情况:它内联Foo::goomain当GCC假装_ZN3Foo3gooEvFoo::goo的mangling之后是两个独立的实体(并因此不能被取代和内联)。

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


但真的没有理由这样做。它只能被视为编译器扩展(因为它使用_Capital符号),如果您需要一种从 C 调用这些函数的方法,请使用void Foo_goo(struct Foo*)您在 C++ 翻译单元中定义的帮助程序。它还可以调用私有成员函数,但是您已经可以使用模板专业化以符合标准的方式执行此操作。

  • @MarquisofLorne UB 是一个标准结构。编译器是一个软件。一个软件具有它所具有的行为,声明它是_未定义_是绝对错误的, (12认同)
  • @MarquisofLorne 当你的目标是编译器时,不存在 UB 这样的东西。 (9认同)
  • @Artyer - `外部“C”`不会禁用名称修改。它说要像 C 编译器那样修改名称。这通常意味着假装下划线,因此声明可能必须省略前导“_”。 (6认同)
  • @MarquisofLorne 好吧,它是 UB,所以它不是有效的 C++,你永远不应该这样做,但无论如何它在这些实现上“有效”。已提供“this”*has*(“has”)的值:它是“f”。从概念上和表面上看,非“虚拟”方法只是一个带有“this”额外参数的自由函数。[这里在 Clang 和 GCC 上没有失败](https://godbolt.org/z/cY516c)。 (3认同)
  • @PasserBy — 行为未定义。这仅意味着语言定义不会告诉您程序的功能。这并不意味着坏事一定会发生。 (3认同)
  • @Henry我只是以“this”为例来说明您可以访问数据成员。`extern "C"` 禁用名称修改(因此它不会声明 `_Z13_ZN3Foo3gooEvP3Foo`) (2认同)
  • @MarquisofLorne 该对象作为“&amp;f”传递。虽然这在技术上不是“调用非静态成员函数”,但使用编译器扩展时,特定名称将损坏为特定符号,这些符号允许通过 C 语言链接调用,因为编译器提供了允许这样做的 ABI。 (2认同)
  • PasserBy 和 @PasserBy:问题是 GCC/clang 是否正式(或当前版本中的事实上)定义和支持这种行为,或者它是否只是一个“碰巧起作用”的东西,可能会因不同的周围代码和/或编译器而中断选项。特定的 C++ 实现 100% 仍然具有 UB。 (2认同)