解开std :: type_info :: name的结果

ter*_*nus 85 c++ gcc name-mangling

我目前正在研究一些日志代码,它们应该 - 除其他外 - 打印有关调用函数的信息.这应该相对容易,标准C++有一个type_info类.它包含typeid'd类/函数/ etc的名称.但它被破坏了.它不是很有用.即typeid(std::vector<int>).name()回归St6vectorIiSaIiEE.

有没有办法从中产生有用的东西?就像std::vector<int>上面的例子一样.如果它只适用于非模板类,那也没关系.

该解决方案应该适用于gcc,但如果我可以移植它会更好.这是为了记录所以它不是那么重要,它不能被关闭,但它应该有助于调试.

Ali*_*Ali 105

鉴于这个问题/答案得到了关注,以及来自GManNickG的有价值的反馈,我已经清理了一些代码.给出了两个版本:一个具有C++ 11特性,另一个仅具有C++ 98特性.

在文件type.hpp中

#ifndef TYPE_HPP
#define TYPE_HPP

#include <string>
#include <typeinfo>

std::string demangle(const char* name);

template <class T>
std::string type(const T& t) {

    return demangle(typeid(t).name());
}

#endif
Run Code Online (Sandbox Code Playgroud)

在文件type.cpp中(需要C++ 11)

#include "type.hpp"
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

std::string demangle(const char* name) {

    int status = -4; // some arbitrary value to eliminate the compiler warning

    // enable c++11 by passing the flag -std=c++11 to g++
    std::unique_ptr<char, void(*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };

    return (status==0) ? res.get() : name ;
}

#else

// does nothing if not g++
std::string demangle(const char* name) {
    return name;
}

#endif
Run Code Online (Sandbox Code Playgroud)

用法:

#include <iostream>
#include "type.hpp"

struct Base { virtual ~Base() {} };

struct Derived : public Base { };

int main() {

    Base* ptr_base = new Derived(); // Please use smart pointers in YOUR code!

    std::cout << "Type of ptr_base: " << type(ptr_base) << std::endl;

    std::cout << "Type of pointee: " << type(*ptr_base) << std::endl;

    delete ptr_base;
}
Run Code Online (Sandbox Code Playgroud)

它打印:

ptr_base的Base*
类型:指针的类型:Derived

使用g ++ 4.7.2测试,克++ 4.9.0 20140302(实验),铛++ 3.4(躯干184647),铛Linux上的64位3.5(躯干202594)和g ++ 4.7.2(MINGW32的Win32 XP SP2).

如果你不能使用C++ 11的功能,这里是如何在C++ 98中完成的,文件type.cpp现在是:

#include "type.hpp"
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

struct handle {
    char* p;
    handle(char* ptr) : p(ptr) { }
    ~handle() { std::free(p); }
};

std::string demangle(const char* name) {

    int status = -4; // some arbitrary value to eliminate the compiler warning

    handle result( abi::__cxa_demangle(name, NULL, NULL, &status) );

    return (status==0) ? result.p : name ;
}

#else

// does nothing if not g++
std::string demangle(const char* name) {
    return name;
}

#endif
Run Code Online (Sandbox Code Playgroud)

(2013年9月8日更新)

接受答案(如2013年9月7日的),当调用abi::__cxa_demangle()成功,返回一个指针到本地,堆栈分配数组 ...哎哟!
另请注意,如果提供缓冲区,则abi::__cxa_demangle()假定它在堆上分配.在堆栈上分配的缓冲区(从GNU DOC)的错误:"如果output_buffer没有足够长的时间,它是使用扩展realloc." 调用realloc()指向堆栈的指针 ......哎哟!(另见Igor Skochinsky的善意评论.)

你可以很容易地验证这两种错误的:是从1024减少缓冲区大小,以接受的答案(如2013年9月7日),以更小的东西,例如16,并给它一个名字的东西超过15(因此realloc()叫).仍然,根据您的系统和编译器优化,输出将是:garbage/nothing/program crash.
要验证第二个错误:将缓冲区大小设置为1并使用名称长度超过1个字符的内容调用它.当你运行它时,程序几乎肯定会在尝试realloc()使用指向堆栈的指针进行调用时崩溃.


(2010年12月27日的旧答案)

KeithB代码进行的重要更改:缓冲区必须由malloc分配或指定为NULL.不要在堆栈上分配它.

检查这种状态也是明智之举.

我没找到HAVE_CXA_DEMANGLE.我检查__GNUG__虽然不能保证代码甚至可以编译.谁有更好的主意?

#include <cxxabi.h>

const string demangle(const char* name) {

    int status = -4;

    char* res = abi::__cxa_demangle(name, NULL, NULL, &status);

    const char* const demangled_name = (status==0)?res:name;

    string ret_val(demangled_name);

    free(res);

    return ret_val;
}
Run Code Online (Sandbox Code Playgroud)

  • 来自[docs](http://idlebox.net/2008/0901-stacktrace-demangled/cxa_demangle.htt):`output_buffer`一个内存区域,用malloc分配,长度为*length bytes,其中demangled名称为存储.如果output_buffer不够长,则使用realloc进行扩展.output_buffer可能改为NULL; 在这种情况下,demangled名称放在使用malloc分配的内存区域中. (2认同)
  • @IgorSkochinsky是的,我之前的评论中有一个拼写错误,但我无法编辑.我想写的是:"上次我检查了'abi :: __ cxa_demangle`,希望它在堆上分配**.**"非常感谢您查找文档! (2认同)
  • 如果使用`std :: unique_ptr <char,decltype(&std :: free)>`作为指针的签名可能会更清楚. (2认同)

moo*_*f2k 24

Boost核心包含一个demangler.结帐core/demangle.hpp:

#include <boost/core/demangle.hpp>
#include <typeinfo>
#include <iostream>

template<class T> struct X
{
};

int main()
{
    char const * name = typeid( X<int> ).name();

    std::cout << name << std::endl; // prints 1XIiE
    std::cout << boost::core::demangle( name ) << std::endl; // prints X<int>
}
Run Code Online (Sandbox Code Playgroud)

它基本上只是一个包装器abi::__cxa_demangle,正如之前所建议的那样.

  • 如果可以选择增强,这是最好的方法! (3认同)

Kei*_*thB 13

这就是我们使用的.HAVE_CXA_DEMANGLE仅在可用时设置(仅限最新版本的GCC).

#ifdef HAVE_CXA_DEMANGLE
const char* demangle(const char* name)
{
   char buf[1024];
    unsigned int size=1024;
    int status;
    char* res = abi::__cxa_demangle (name,
                                 buf,
                                 &size,
                                 &status);
    return res;
  }
#else
const char* demangle(const char* name)
{
  return name;
}
#endif  
Run Code Online (Sandbox Code Playgroud)

  • 警告!上面的代码可能会导致程序崩溃.缓冲区必须由malloc分配或指定为NULL.不要在堆栈上分配它.请参阅下面的代码. (19认同)
  • 你需要包含`#include <cxxabi.h>`. (6认同)

Joh*_*itb 8

在这里,看一下type_strings.hpp,它包含一个可以完成你想要的功能.

如果您只是寻找一个demangling工具,您可以使用它来破坏日志文件中显示的内容,请查看c++filtbinutils附带的内容.它可以解析C++和Java符号名称.

  • 链接到type_strings.cpp似乎坏了. (4认同)

Ecl*_*pse 5

它是实现定义的,所以它不是可移植的。在 MSVC++ 中,name() 是未修饰的名称,您必须查看 raw_name() 才能获得修饰的名称。
这里只是暗中刺伤,但在 gcc 下,您可能想查看demangle.h


Hum*_*ler 5

如果我们想要的只是用于记录目的的未处理类型名称,我们实际上可以在不使用std::type_info甚至不使用RTTI 的情况下做到这一点。

适用于三大主要编译器前端()的稍微可移植的解决方案是使用函数template并从函数名称中提取类型名称。

gcc并且clang两者都提供__PRETTY_FUNCTION__当前函数或函数模板的名称,字符串中包含所有类型参数。类似地,MSVC 具有__FUNCSIG__等效性。其中每一个的格式都略有不同,例如,对于 的调用void foo<int>,编译器将输出不同的内容:

  • gcc 被格式化 void foo() [with T = int; ]
  • clang 被格式化 void foo() [T = int]
  • msvc 被格式化 void foo<int>()

知道了这一点,只需解析出前缀和后缀并将其包装到函数中即可提取出类型名称。

我们甚至可以使用std::string_view和扩展constexpr在获得字符串名称编译时只需通过解析一个模板函数的名称。这也可以在任何早期的 C++ 版本中完成,但这仍然需要某种形式的字符串解析。

例如:

#include <string_view>

template <typename T>
constexpr auto get_type_name() -> std::string_view
{
#if defined(__clang__)
    constexpr auto prefix = std::string_view{"[T = "};
    constexpr auto suffix = "]";
    constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(__GNUC__)
    constexpr auto prefix = std::string_view{"with T = "};
    constexpr auto suffix = "; ";
    constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
#elif defined(__MSC_VER)
    constexpr auto prefix = std::string_view{"get_type_name<"};
    constexpr auto suffix = ">(void)";
    constexpr auto function = std::string_view{__FUNCSIG__};
#else
# error Unsupported compiler
#endif

    const auto start = function.find(prefix) + prefix.size();
    const auto end = function.find(suffix);
    const auto size = end - start;

    return function.substr(start, size);
}
Run Code Online (Sandbox Code Playgroud)

有了这个,您可以在编译时调用get_type_name<T>()以获取std::string_view指示未处理的类型名称。

例如:

std::cout << get_type_name<std::string>() << std::endl;
Run Code Online (Sandbox Code Playgroud)

在 GCC 上将输出:

std::__cxx11::basic_string<char>
Run Code Online (Sandbox Code Playgroud)

并且在 clang 将输出:

std::basic_string<char>
Run Code Online (Sandbox Code Playgroud)

Live Example


与此方法类似的增强是避免 aprefixsuffix假设所有类型的函数名称都相同,并搜索哨兵类型以解析从每一端到哨兵的偏移量。这确保字符串搜索只发生一次,并且每次都假定偏移量找到字符串名称。例如,double用作一个简单的哨兵:

std::__cxx11::basic_string<char>
Run Code Online (Sandbox Code Playgroud)

Live Example


这并非对所有编译器都具有可移植性,但可以针对提供__FUNCSIG__/__PRETTY_FUNCTION__等效项的任何编译器进行修改;它只需要一点解析。

注意:这还没有经过全面测试,所以可能存在一些错误;但主要的想法是解析所有包含名称的输出——这通常是__func__编译器上类似输出的副作用。

  • 同意!希望 C++23 最终包含静态反射支持,这样人们就不必依赖这种笨拙的方法 (6认同)
  • 遗憾的是,即使在 2021 年,它也需要大量的样板代码才能得到 C++ 分解:-( (4认同)