【问题标题】:Is the return type of a function part of the mangled name?函数的返回类型是损坏名称的一部分吗?
【发布时间】:2025-12-04 14:15:03
【问题描述】:

假设我有两个具有相同参数类型和名称的函数(不在同一个程序中):

std::string foo(int x) {
  return "hello"; 
}

int foo(int x) {
  return x;
}

编译后它们会有相同的重命名吗?

返回类型是 C++ 中重整名称的一部分吗?

【问题讨论】:

  • 也许,也许不是。这取决于编译器。
  • 达尔文解决了这个问题。不允许链接完全错误的函数或产生(几乎)无法诊断的链接器错误的编译器。

标签: c++ name-mangling


【解决方案1】:

由于修改方案没有标准化,因此这个问题没有单一的答案;最接近实际答案的方法是查看由最常见的重整方案生成的重整名称。据我所知,这些是 GCC 和 MSVC 方案,按字母顺序排列,所以...


海合会:

要对此进行测试,我们可以使用一个简单的程序。

#include <string>
#include <cstdlib>

std::string foo(int x) { return "hello"; }
//int         foo(int x) { return x; }

int main() {
    // Assuming executable file named "a.out".
    system("nm a.out");
}

使用 GCC 或 Clang 编译和运行,它会列出它包含的符号。根据未注释的函数,结果将是:

// GCC:
// ----

std::string foo(int x) { return "hello"; } // _Z3fooB5cxx11i
                                             // foo[abi:cxx11](int)
int         foo(int x) { return x; }       // _Z3fooi
                                             // foo(int)

// Clang:
// ------

std::string foo(int x) { return "hello"; } // _Z3fooi
                                             // foo(int)
int         foo(int x) { return x; }       // _Z3fooi
                                             // foo(int)

GCC 方案包含的信息相对较少,不包括返回类型:

  • 符号类型:_Z 表示“函数”。
  • 姓名:3foo 代表::foo
  • 参数:iint

尽管如此,但它们在使用 GCC 编译时(而不是使用 Clang)是不同的,因为 GCC 表明 std::string 版本使用 cxx11 ABI。

请注意,它仍然会跟踪返回类型,并确保签名匹配;它只是不使用函数的重命名来执行此操作。


MSVC:

为了测试这一点,我们可以使用一个简单的程序,如上所述。

#include <string>
#include <cstdlib>
    
std::string foo(int x) { return "hello"; }
//int         foo(int x) { return x; }
    
int main() {
    // Assuming object file named "a.obj".
    // Pipe to file, because there are a lot of symbols when <string> is included.
    system("dumpbin/symbols a.obj > a.txt");
}

使用 Visual Studio 编译和运行,a.txt 将列出它包含的符号。根据未注释的函数,结果将是:

std::string foo(int x) { return "hello"; }
  // ?foo@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@H@Z
  // class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > __cdecl foo(int)
int         foo(int x) { return x; }
  // ?foo@@YAHH@Z
  // int __cdecl foo(int)

MSVC 方案包含整个声明,包括未明确指定的内容:

  • 名称:foo@ 代表 ::foo,后跟 @ 以终止。
  • 符号类型:名称终止 @ 之后的所有内容。
  • 类型和成员状态:Y 表示“非成员函数”。
  • 调用约定:A for __cdecl
  • 返回类型:
    • Hint
    • ?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@(后跟@ 终止)为std::basic_string&lt;char, std::char_traits&lt;char&gt;, std::allocator&lt;char&gt;&gt;(简称std::string)。
  • 参数列表:Hint(后跟@终止)。
  • 异常说明符:Z for throw(...);除非是其他名称,否则此名称会从解构名称中省略,可能是因为 MSVC 无论如何都会忽略它。

如果每个编译单元的声明不同,它就会对你发牢骚。


通常,大多数编译器在分别针对 *nix 或 Windows 时会使用其中一种方案(或有时是其变体),但这并不能保证。比如……

  • 据我所知,Clang 将为 *nix 使用 GCC 方案,或为 Windows 使用 MSVC 方案。
  • 英特尔 C++ 在 Linux 和 Mac 上使用 GCC 方案,在 Windows 上使用 MSVC 方案(有一些小的变化)。
  • Borland 和 Watcom 编译器有自己的方案。
  • Symantec 和 Digital Mars 编译器通常使用 MSVC 方案,但有一些小的改动。
  • 旧版本的 GCC 和许多 UNIX 工具使用 cfront 修改方案的修改版本。
  • 等等……

其他编译器使用的方案感谢Agner Fog's PDF


注意:

检查生成的符号,很明显 GCC 的修改方案没有提供与 MSVC 相同级别的针对 Machiavelli 的保护。考虑以下几点:

// foo.cpp
#include <string>

// Simple wrapper class, to avoid encoding `cxx11 ABI` into the GCC name.
class MyString {
    std::string data;

  public:
    MyString(const char* const d) : data(d) {}

    operator std::string() { return data; }
};

// Evil.
MyString foo(int i) { return "hello"; }

// -----

// main.cpp
#include <iostream>

// Evil.
int foo(int);

int main() {
    std::cout << foo(3) << '\n';
}

如果我们分别编译每个源文件,然后尝试将目标文件链接在一起......

  • GCC:MyString,由于不是cxx11 ABI 的一部分,导致MyString foo(int) 被破坏为_Z3fooi,就像int foo(int)。这允许链接目标文件,并生成可执行文件。尝试运行它会导致段错误。
  • MSVC:链接器将寻找?foo@@YAHH@Z;因为我们提供了?foo@@YA?AVMyString@@H@Z,所以链接将失败。

考虑到这一点,包含返回类型的修饰方案更安全,即使函数不能仅根据返回类型的差异进行重载。

【讨论】:

    【解决方案2】:

    不,我希望它们的名称与所有现代编译器相同。更重要的是,在同一个程序中使用它们会导致未定义的行为。 C++ 中的函数不能只在返回类型上有所不同。

    【讨论】:

    • 实际上有 2 个错误。 1) Visual Studio 错位名称包含实体的完整类型;这些函数分别是?foo@@YA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@H@Z?foo@@YAHH@Z。类似地,GCC 重整名称也包含一些类型信息,尽管不如 MSVC 名称多;这些函数分别是_Z3fooB5cxx11i_Z3fooi(不保留返回类型,但std::string 标识它使用cxx11 ABI)。 (在某些版本的 GCC 上它们可能是相同的。)
    • 2) 没有理由假设所有编译器都使用相同的修饰方案;每个编译器都使用自己的编译器是完全有可能的(并且有一定的可能)。例如,Visual Studio 和 GCC 都有自己的修改方案,大量其他编译器分别使用它们的方案作为 Windows 和 *nix 的非官方“标准”。 Borland 和 Watcom 编译器也有自己独特的方案,旧版本的 GCC(以及许多 UNIX 工具)使用 cfront 的修改方案的修改版本。
    • 英特尔编译器在 Windows 上通常使用 MSVC 方案,在 *nix 上使用现代 GCC 方案。赛门铁克和 Digital Mars 一般使用 MSVC 方案,有一些小的改动。等等。见here,有点意思。
    • 据我所知,它们都不会真正编译代码,但如果每个函数都是单独编译的,则不能保证它们的名称相同。
    • 我同意你的观点,因为返回类型必须是唯一的。无论如何,修改返回类型会增加额外的安全性,在不太可能的情况下,单独的编译单元定义了这样的冲突函数而没有在同一个单元中声明它们。