函数的 return 类型是错位名称的一部分吗?
Is the return type of a function part of the mangled name?
假设我有两个具有相同参数类型和名称的函数(不在同一个程序中):
std::string foo(int x) {
return "hello";
}
int foo(int x) {
return x;
}
它们在编译后会有相同的错位名称吗?
return 类型是 C++ 中损坏名称的一部分吗?
不,我希望所有现代编译器的名称都相同。更重要的是,在同一个程序中使用它们会导致未定义的行为。 C++ 中的函数不能仅在 return 类型上有所不同。
由于处理方案没有标准化,这个问题没有单一的答案;最接近实际答案的是查看由最常见的改编方案生成的改编名称。据我所知,这些是 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 方案包含的信息相对较少,不包括 return 类型:
- 符号类型:
_Z
用于“函数”。
- 姓名:
3foo
::foo
。
- 参数:
i
用于 int
。
尽管如此,它们在使用 GCC(但不是使用 Clang)编译时是不同的,因为 GCC 指示 std::string
版本使用 cxx11
ABI。
请注意,它仍然会跟踪 return 类型,并确保签名匹配;它只是不使用函数的损坏名称来这样做。
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@
for ::foo
,后跟 @
终止。
- 符号类型:名称终止后的所有内容
@
。
- 类型和成员状态:
Y
“非成员函数”。
- 调用约定:
A
用于 __cdecl
。
- Return 输入:
H
int
.
?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@
(后接@
终止)为std::basic_string<char, std::char_traits<char>, std::allocator<char>>
(简称std::string
)。
- 参数列表:
H
for int
(后跟@
终止)。
- 异常说明符:
Z
for throw(...)
;除非它是别的东西,否则它会从 demangled 名称中省略,可能是因为 MSVC 无论如何都会忽略它。
如果每个编译单元的声明不相同,这允许它向您发牢骚。
通常,大多数编译器在针对 *nix 或 Windows 时会分别使用其中一种方案(或有时是其变体),但这并不能保证。例如...
- 据我所知,Clang 将为 *nix 使用 GCC 方案,或为 Windows.
使用 MSVC 方案
- Intel 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';
}
如果我们分别编译每个源文件,然后尝试 link 目标文件一起...
- GCC:
MyString
,由于不是 cxx11
ABI 的一部分,导致 MyString foo(int)
被破坏为 _Z3fooi
,就像 int foo(int)
.这允许 linked 目标文件,并生成可执行文件。尝试 运行 它会导致段错误。
- MSVC:linker 将查找
?foo@@YAHH@Z
;因为我们提供 ?foo@@YA?AVMyString@@H@Z
,linking 将失败。
考虑到这一点,包含 return 类型的重整方案更安全,即使不能仅根据 return 类型的差异重载函数。
假设我有两个具有相同参数类型和名称的函数(不在同一个程序中):
std::string foo(int x) {
return "hello";
}
int foo(int x) {
return x;
}
它们在编译后会有相同的错位名称吗?
return 类型是 C++ 中损坏名称的一部分吗?
不,我希望所有现代编译器的名称都相同。更重要的是,在同一个程序中使用它们会导致未定义的行为。 C++ 中的函数不能仅在 return 类型上有所不同。
由于处理方案没有标准化,这个问题没有单一的答案;最接近实际答案的是查看由最常见的改编方案生成的改编名称。据我所知,这些是 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 方案包含的信息相对较少,不包括 return 类型:
- 符号类型:
_Z
用于“函数”。 - 姓名:
3foo
::foo
。 - 参数:
i
用于int
。
尽管如此,它们在使用 GCC(但不是使用 Clang)编译时是不同的,因为 GCC 指示 std::string
版本使用 cxx11
ABI。
请注意,它仍然会跟踪 return 类型,并确保签名匹配;它只是不使用函数的损坏名称来这样做。
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@
for::foo
,后跟@
终止。 - 符号类型:名称终止后的所有内容
@
。 - 类型和成员状态:
Y
“非成员函数”。 - 调用约定:
A
用于__cdecl
。 - Return 输入:
H
int
.?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@
(后接@
终止)为std::basic_string<char, std::char_traits<char>, std::allocator<char>>
(简称std::string
)。
- 参数列表:
H
forint
(后跟@
终止)。 - 异常说明符:
Z
forthrow(...)
;除非它是别的东西,否则它会从 demangled 名称中省略,可能是因为 MSVC 无论如何都会忽略它。
如果每个编译单元的声明不相同,这允许它向您发牢骚。
通常,大多数编译器在针对 *nix 或 Windows 时会分别使用其中一种方案(或有时是其变体),但这并不能保证。例如...
- 据我所知,Clang 将为 *nix 使用 GCC 方案,或为 Windows. 使用 MSVC 方案
- Intel 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';
}
如果我们分别编译每个源文件,然后尝试 link 目标文件一起...
- GCC:
MyString
,由于不是cxx11
ABI 的一部分,导致MyString foo(int)
被破坏为_Z3fooi
,就像int foo(int)
.这允许 linked 目标文件,并生成可执行文件。尝试 运行 它会导致段错误。 - MSVC:linker 将查找
?foo@@YAHH@Z
;因为我们提供?foo@@YA?AVMyString@@H@Z
,linking 将失败。
考虑到这一点,包含 return 类型的重整方案更安全,即使不能仅根据 return 类型的差异重载函数。