在软件开发中,特别是在使用C++这类静态类型语言时,编译器在编译过程中会生成许多内部表示,包括类型信息。这些内部类型名通常用于编译器的内部处理,比如类型检查、优化和代码生成等。
然而,在编写源代码或进行调试时,我们更习惯于使用人类可读和易于理解的类型名。比如我们不太能看懂_ZSt10_ConstructIN11composition6TalkerEJRN6rclcpp11NodeOptionsEEEvPT_DpOT0_
,但是可以看懂void std::_Construct<composition::Talker, rclcpp::NodeOptions&>(composition::Talker*, rclcpp::NodeOptions&)
。
因此,将C++编译器生成的类型名转换成代码中的类型名具有多方面的必要性:
-
提高代码的可读性。源代码中的类型名应该清晰地反映其意图和用途,以便于开发者和维护者理解。编译器生成的类型名往往包含编译器特有的前缀、后缀或编码,这使得它们难以被人类直接理解。通过转换成代码中的类型名,可以大大提高代码的可读性和可维护性。
-
促进团队合作。在团队开发环境中,不同的开发者可能使用不同的编译器或编译器版本。如果直接使用编译器生成的类型名,可能会导致类型名在不同环境下不一致,进而增加团队合作的难度。而将类型名转换为代码中定义的类型名,可以确保所有开发者在查看和理解代码时使用的是相同的类型名,从而降低沟通成本和错误率。
-
简化调试过程。在调试过程中,开发者经常需要查看变量的类型和值。如果变量使用的是编译器生成的类型名,那么在调试器中查找和识别这些类型可能会变得困难。而将类型名转换为代码中定义的类型名,可以使调试过程更加直观和高效。
下面我们将通过代码编写一个工具,负责将这些编译器生成的类型名转换成代码中的类型名。
代码
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
#include <memory>
#include <vector>
// 辅助函数:解码类型名
std::string demangle(const char* name) {
int status = 0;
char* demangled = abi::__cxa_demangle(name, nullptr, nullptr, &status);
std::string result = (status == 0) ? demangled : name;
free(demangled);
return result;
}
int main(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
std::cout << "argv[" << i << "]" << std::endl << argv[i] << " -> " << demangle(argv[i]) << std::endl << std::endl;
}
return 0;
}
测试
我们输入《Robot Operating System——深度解析通过符号和隐式加载动态库的运行模式》一文中追踪的符号。
./build/DemangleExample _ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE ZSt10_ConstructIN11composition6TalkerEJRN6rclcpp11NodeOptionsEEEvPT_DpOT0 ZNSt23_Sp_counted_ptr_inplaceIN11composition6TalkerESaIvELN9__gnu_cxx12_Lock_policyE2EEC1IJRN6rclcpp11NodeOptionsEEEES2_DpOT ZNSt14__shared_countILN9__gnu_cxx12_Lock_policyE2EEC1IN11composition6TalkerESaIvEJRN6rclcpp11NodeOptionsEEEERPT_St20_Sp_alloc_shared_tagIT0_EDpOT1 ZNSt12__shared_ptrIN11composition6TalkerELN9__gnu_cxx12_Lock_policyE2EEC1ISaIvEJRN6rclcpp11NodeOptionsEEEESt20_Sp_alloc_shared_tagIT_EDpOT0 ZNSt10shared_ptrIN11composition6TalkerEEC1ISaIvEJRN6rclcpp11NodeOptionsEEEESt20_Sp_alloc_shared_tagIT_EDpOT0
然后我们会看到如下结果
argv[1]
_ZN11composition6TalkerC1ERKN6rclcpp11NodeOptionsE -> composition::Talker::Talker(rclcpp::NodeOptions const&)
argv[2]
ZSt10_ConstructIN11composition6TalkerEJRN6rclcpp11NodeOptionsEEEvPT_DpOT0 -> void std::_Construct<composition::Talker, rclcpp::NodeOptions&>(composition::Talker*, rclcpp::NodeOptions&)
argv[3]
ZNSt23_Sp_counted_ptr_inplaceIN11composition6TalkerESaIvELN9__gnu_cxx12_Lock_policyE2EEC1IJRN6rclcpp11NodeOptionsEEEES2_DpOT -> std::_Sp_counted_ptr_inplace<composition::Talker, std::allocator<void>, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplacerclcpp::NodeOptions&(std::allocator<void>, rclcpp::NodeOptions&)
argv[4]
ZNSt14__shared_countILN9__gnu_cxx12_Lock_policyE2EEC1IN11composition6TalkerESaIvEJRN6rclcpp11NodeOptionsEEEERPT_St20_Sp_alloc_shared_tagIT0_EDpOT1 -> std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<composition::Talker, std::allocator<void>, rclcpp::NodeOptions&>(composition::Talker*&, std::_Sp_alloc_shared_tag<std::allocator<void> >, rclcpp::NodeOptions&)
argv[5]
ZNSt12__shared_ptrIN11composition6TalkerELN9__gnu_cxx12_Lock_policyE2EEC1ISaIvEJRN6rclcpp11NodeOptionsEEEESt20_Sp_alloc_shared_tagIT_EDpOT0 -> std::__shared_ptr<composition::Talker, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<void>, rclcpp::NodeOptions&>(std::_Sp_alloc_shared_tag<std::allocator<void> >, rclcpp::NodeOptions&)
argv[6]
ZNSt10shared_ptrIN11composition6TalkerEEC1ISaIvEJRN6rclcpp11NodeOptionsEEEESt20_Sp_alloc_shared_tagIT_EDpOT0 -> std::shared_ptr<composition::Talker>::shared_ptr<std::allocator<void>, rclcpp::NodeOptions&>(std::_Sp_alloc_shared_tag<std::allocator<void> >, rclcpp::NodeOptions&)
这样整个符号就容易理解了。
代码地址
https://github.com/f304646673/cpulsplus/tree/master/demangled