函数的返回类型是错位名称的一部分吗?

时间:2016-11-24 16:47:28

标签: c++ name-mangling

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

ifelse

编译后,它们是否会有相同的错位名称?

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

2 个答案:

答案 0 :(得分:4)

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

GCC

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

#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
  • i的参数:int

尽管如此,但是在使用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调用约定:__cdecl
  • 返回类型:
    • 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的参数列表:int(后跟@终止)。
  • Z的例外说明符:throw(...);除非它是其他东西,否则这个被删除的名称省略,可能是因为MSVC无论如何都忽略了它。

如果每个编译单元的声明不相同,这可以让你发牢骚。

通常,大多数编译器在分别以* nix或Windows为目标时会使用其中一种方案(有时是其变体),但这不能保证。例如......

  • 据我所知,Clang将使用GCC方案用于* nix,或者用于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),就像?foo@@YAHH@Z一样。这允许链接目标文件,并生成可执行文件。试图运行它会导致段错误。
  • MSVC:链接器将查找?foo@@YA?AVMyString@@H@Z;因为我们提供 import UIKit import GoogleMaps class FirstViewController: UIViewController, CLLocationManagerDelegate { @IBOutlet weak var mapView: GMSMapView! var locationManager = CLLocationManager() var vwGMap = GMSMapView() override func viewDidLoad() { super.viewDidLoad() let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: 22.300000, longitude: 70.783300, zoom: 10.0) vwGMap = GMSMapView.map(withFrame: self.view.frame, camera: camera) vwGMap.camera = camera locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyKilometer locationManager.distanceFilter = 500 locationManager.requestWhenInUseAuthorization() locationManager.requestAlwaysAuthorization() locationManager.startUpdatingLocation() self.view = vwGMap } func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { if (status == CLAuthorizationStatus.authorizedWhenInUse) { vwGMap.isMyLocationEnabled = true } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let newLocation = locations.last vwGMap.camera = GMSCameraPosition.camera(withTarget: newLocation!.coordinate, zoom: 15.0) vwGMap.settings.myLocationButton = true self.view = self.vwGMap let marker = GMSMarker() marker.position = CLLocationCoordinate2DMake(newLocation!.coordinate.latitude, newLocation!.coordinate.longitude) marker.map = self.vwGMap } ,链接将失败。

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

答案 1 :(得分:1)

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