Распутывание результата std :: type_info :: name


97

В настоящее время я работаю над некоторым кодом регистрации, который должен, среди прочего, печатать информацию о вызывающей функции. Это должно быть относительно просто, стандартный C ++ имеет type_infoкласс. Он содержит имя typeid'd класса / функции / и т. Д. но это искалечено. Это не очень полезно. Т.е. typeid(std::vector<int>).name()возвращается St6vectorIiSaIiEE.

Есть ли способ извлечь из этого что-нибудь полезное? Как std::vector<int>в приведенном выше примере. Если это работает только для классов, не являющихся шаблонами, это тоже нормально.

Решение должно работать для gcc, но было бы лучше, если бы я мог его портировать. Это для ведения журнала, поэтому не так важно, чтобы его нельзя было отключить, но он должен быть полезен для отладки.

Ответы:


119

Учитывая внимание, которое уделяется этому вопросу / ответу, и ценные отзывы от 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

В файле 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

Применение:

#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;
}

Он печатает:

Тип ptr_base: Base*
Тип указателя:Derived

Протестировано с g ++ 4.7.2, g ++ 4.9.0 20140302 (экспериментально), clang ++ 3.4 (магистраль 184647), clang 3.5 (магистраль 202594) на 64-битной Linux и 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


(Обновление от 8 сентября 2013 г.)

Принятый ответ (по состоянию на 7 сентября 2013 г.) , когда вызов abi::__cxa_demangle()успешен, возвращает указатель на локальный массив, выделенный стеком ... ой!
Также обратите внимание, что если вы предоставляете буфер, abi::__cxa_demangle()предполагается , что он размещен в куче. Выделение буфера в стеке является ошибкой (из документации GNU): «Если output_bufferнедостаточно, он расширяется с помощью realloc Вызов realloc()указателя на стек ... ой! (См. Также добрый комментарий Игоря Скочинского .)

Вы можете легко проверить обе эти ошибки: просто уменьшите размер буфера в принятом ответе (по состоянию на 7 сентября 2013 г.) с 1024 до меньшего, например 16, и дайте ему что-нибудь с именем не длиннее 15 (так realloc()же не называется). Тем не менее, в зависимости от вашей системы и оптимизации компилятора, вывод будет: мусор / ничего / сбой программы.
Чтобы проверить вторую ошибку: установите размер буфера равным 1 и вызовите его с чем-то, имя которого длиннее 1 символа. Когда вы его запускаете, программа почти наверняка вылетает при попытке вызова realloc()с указателем на стек.


(Старый ответ от 27 декабря 2010 г.)

Важные изменения, внесенные в код 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;
}

Обратите внимание, что для этого требуется #include <cxxabi.h>. В противном случае отлично работало, спасибо.
Терраса

2
Из документации : output_bufferОбласть памяти, выделенная с помощью malloc, размером * байтов, в которой хранится разоблаченное имя. Если output_buffer недостаточно длинный, он расширяется с помощью realloc. output_buffer вместо этого может быть NULL; в этом случае разоблаченное имя помещается в область памяти, выделенную с помощью malloc.
Игорь Скочинский

2
@IgorSkochinsky Да, в моем предыдущем комментарии есть опечатка, но я не могу ее отредактировать. Что я хотел написать: «В прошлый раз я abi::__cxa_demangleожидал, что он будет размещен в куче». Большое спасибо за просмотр документа!
Али

1
Обратите внимание, что технически это может протечь, если ret_valбросить во время строительства. Вы можете использовать защиту прицела, чтобы защититься от этого.
GManNickG

3
Если бы, вероятно, было бы более понятно использовать std::unique_ptr<char, decltype(&std::free)>в качестве подписи для вашего указателя.
mindvirus

29

Ядро Boost содержит деманглер. Оформить заказ 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>
}

По сути, это просто оболочка для abi::__cxa_demangle, как предлагалось ранее.


1
Если ускорение - вариант, это лучший способ!
hbobenicio

14

Это то, что мы используем. 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  

6
Вам нужно включить #include <cxxabi.h>.
fuenfundachtzig

Интересно. У меня __cxa_demangle без определения
HAVE_CXA_DEMANGLE

@Matt Я хотел сказать, что наша система сборки, основанная на autoconf, устанавливает HAVE_CXA_DEMANGLE только в том случае, если она доступна.
KeithB

19
ПРЕДУПРЕЖДЕНИЕ! Приведенный выше код может привести к сбою программы. Буфер должен быть либо выделен функцией malloc, либо указан как NULL. НЕ размещайте его в стеке. Смотрите мой код ниже.
Али


9

Здесь взгляните на type_strings.hpp, он содержит функцию, которая делает то, что вы хотите.

Если вы просто ищете инструмент для разборки цепочек, который вы, например, могли бы использовать для искажения информации, отображаемой в файле журнала, взгляните на него c++filt, который поставляется с binutils. Он может распутывать имена символов C ++ и Java.


Стоит отметить, что как cxa_demange () (которую использует связанный код), так и cx ++ filter специфичны для gcc. Нет портативного способа сделать это.
KeithB

c ++ filter не помогает, мне нужно это (или большая часть) во время компиляции, в основном это делается с помощью макросов.
конечная остановка

4
Ссылка на type_strings.cpp кажется неработающей.
StackedCrooked

1
Привет, @GregoryPakosz Ссылка на github в вашем комментарии тоже кажется неработающей :( Ура
oHo 06

Просто важный к сведению: abi::__cxa_demangle()и подобные <cxxabi.h> им не являются специфичными для GCC - они, возможно, были только для GCC в далеком прошлом, но на момент написания этого поста <cxxabi.h>были укоренившимся специальным стандартом. Итак, хотя ссылка на код ответа была DOI, я могу поручиться за Clang, обеспечивающую первоклассную поддержку в этом случае ... qv, из libcxxabiисточника Clang : соответствующий decl, impl, огромный тест: git.io/vRTBo , git.io/vRTBh , git.io/vRTRf - комментарии тестового кода отмечают, что реализация Clang способна каким-то образом более детально разбираться, чем GCC.
fish2000 02

5

Его реализация определена, так что переносимость не будет. В MSVC ++ name () - это недекорированное имя, и вам нужно посмотреть на raw_name (), чтобы получить декорированное.
Здесь просто удар в темноте, но в gcc вы можете посмотреть demangle.h


3

Я также нашел макрос с именем __PRETTY_FUNCTION__, который делает свое дело. Это дает красивое имя функции (цифры :)). Это то, что мне было нужно.

Т.е. это дает мне следующее:

virtual bool mutex::do_unlock()

Но я не думаю, что это работает на других компиляторах.


Да, PRETTY_FUNCTION зависит от gcc.
Грег Роджерс

2

Это не полное решение, но вы можете посмотреть, что определяют некоторые стандартные (или широко поддерживаемые) макросы. В коде ведения журнала часто можно увидеть использование макросов:

__FUNCTION__
__FILE__
__LINE__

e.g.:

log(__FILE__, __LINE__, __FUNCTION__, mymessage);

4
Не говоря уже о PRETTY_FUNCTION .
CesarB

1
Это даст вам информацию о том, где вы находитесь в коде. Вопрос был задан красивым названием типа, например std :: vector.
KeithB

Он упомянул, что это было для отладки, и я заявил, что это не полное решение. Другие макросы, такие как FUNCDNAME , вернут оформленное имя.
люк

Фактически, перечитывая вопрос, это было: «Я сейчас работаю над некоторым кодом регистрации, который должен, помимо прочего, печатать информацию о вызывающей функции». Это работает.
Макс Либберт

Это не все, так как я не знаю пространства имен. Это уже есть в моем коде. Но все равно спасибо.
конечная остановка,

2

Небольшая вариация решения Али. Если вы хотите, чтобы код оставался очень похожим на

typeid(bla).name(),

писать это вместо

Typeid(bla).name() (отличается только первой заглавной буквой)

тогда вам может быть интересно это:

В файле 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());
}
*/

class Typeid {
 public:

  template <class T>
    Typeid(const T& t) : typ(typeid(t)) {}

  std::string name() { return demangle(typ.name()); }

 private:
  const std::type_info& typ;
};


#endif

type.cpp остается таким же, как в решении Али


1

Взгляните на то, __cxa_demangleчто вы можете найти на cxxabi.h.


Брал, устарел, судя по полученному сообщению.
конечная остановка

Где вы нашли это сообщение? Я просто погуглил, и, похоже, он поддерживается, никаких доказательств того, что он устарел.
Али

Возможно, он устарел в :: namespace. Используйте abi :: __ cxa_demangle, и вы не получите предупреждения. Какой gcc вы используете?
onitake

1
// KeithB's solution is good, but has one serious flaw in that unless buf is static
// it'll get trashed from the stack before it is returned in res - and will point who-knows-where
// Here's that problem fixed, but the code is still non-re-entrant and not thread-safe.
// Anyone care to improve it?

#include <cxxabi.h>

// todo: javadoc this properly
const char* demangle(const char* name)
{
    static char buf[1024];
    size_t size = sizeof(buf);
    int status;
    // todo:
    char* res = abi::__cxa_demangle (name,
                                 buf,
                                 &size,
                                 &status);
    buf[sizeof(buf) - 1] = 0; // I'd hope __cxa_demangle does this when the name is huge, but just in case.
    return res;
  }

11
ПРЕДУПРЕЖДЕНИЕ! Буфер должен быть либо выделен функцией malloc, либо указан как NULL. НЕ размещайте его в стеке. Смотрите мой код ниже.
Али

1

Общепринятое решение [1] работает в основном хорошо. Я нашел по крайней мере один случай (и я бы не назвал его угловым), когда он не сообщает того, чего я ожидал ... со ссылками.

Для этих случаев я нашел другое решение, размещенное внизу.

Проблемный случай (использование, typeкак определено в [1]):

int i = 1;
cout << "Type of " << "i" << " is " << type(i) << endl;
int & ri = i;
cout << "Type of " << "ri" << " is " << type(ri) << endl;

производит

Type of i is int
Type of ri is int

Решение (использование type_name<decltype(obj)>(), см. Код ниже):

cout << "Type of " << "i" << " is " << type_name<decltype(i)>() << endl;
cout << "Type of " << "ri" << " is " << type_name<decltype(ri)>() << endl;

производит

Type of i is int
Type of ri is int&

по желанию (по крайней мере, мной)

Код . Он должен быть во включенном заголовке, а не в отдельно скомпилированном источнике из-за проблем со специализацией. См., Например, неопределенную ссылку на функцию шаблона .

#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
}

0

Я всегда хотел использовать type_info, но уверен, что результат функции-члена name () нестандартен и не обязательно вернет что-либо, что можно преобразовать в значимый результат.
Если вы придерживаетесь одного компилятора, возможно, есть специальная функция компилятора, которая будет делать то, что вы хотите. Проверить документацию.


0

Следуя решению Али, вот шаблонная альтернатива C ++ 11, которая лучше всего подходит для моего использования.

// type.h
#include <cstdlib>
#include <memory>
#include <cxxabi.h>

template <typename T>
std::string demangle() {
  int status = -4;

  std::unique_ptr<char, void (*)(void*)> res{
      abi::__cxa_demangle(typeid(T).name(), NULL, NULL, &status), std::free};
  return (status == 0) ? res.get() : typeid(T).name();
}

Применение:

// main.cpp
#include <iostream>

namespace test {
    struct SomeStruct {};
}

int main()
{
    std::cout << demangle<double>() << std::endl;
    std::cout << demangle<const int&>() << std::endl;
    std::cout << demangle<test::SomeStruct>() << std::endl;

    return 0;
}

Напечатаем:

double                                                                        
int                                                                           
test::SomeStruct
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.