Rozpakowywanie wyniku std :: type_info :: name


97

Obecnie pracuję nad kodem do logowania, który miałby - między innymi - wypisywać informacje o funkcji wywołującej. Powinno to być stosunkowo łatwe, standardowy C ++ ma type_infoklasę. Zawiera nazwę klasy / funkcji / itp. Z identyfikatorem typu. ale jest zniekształcony. Nie jest to zbyt przydatne. Tj . typeid(std::vector<int>).name()Wraca St6vectorIiSaIiEE.

Czy jest sposób na stworzenie z tego czegoś pożytecznego? Jak std::vector<int>w powyższym przykładzie. Jeśli działa tylko dla klas innych niż szablon, to też jest w porządku.

Rozwiązanie powinno działać dla gcc, ale byłoby lepiej, gdybym mógł je przenieść. Służy do rejestrowania, więc nie jest tak ważne, aby nie można było go wyłączyć, ale powinno być pomocne przy debugowaniu.

Odpowiedzi:


119

Biorąc pod uwagę uwagę , jaką wzbudza to pytanie / odpowiedź, oraz cenne opinie od GManNickG , trochę wyczyściłem kod. Podano dwie wersje: jedną z funkcjami C ++ 11 i drugą z funkcjami C ++ 98.

W pliku 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

W pliku type.cpp (wymaga 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

Stosowanie:

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

Drukuje:

Typ ptr_base: Base*
Typ pointee :Derived

Testowane z g ++ 4.7.2, g ++ 4.9.0 20140302 (eksperymentalne), clang ++ 3.4 (trunk 184647), clang 3.5 (trunk 202594) w systemie Linux 64-bit oraz g ++ 4.7.2 (Mingw32, Win32 XP SP2).

Jeśli nie możesz korzystać z funkcji C ++ 11, oto jak można to zrobić w C ++ 98, plik type.cpp ma teraz postać :

#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


(Aktualizacja z 8 września 2013 r.)

Zaakceptowana odpowiedź (stan na 7 września 2013 r.) , Gdy wywołanie się abi::__cxa_demangle()powiedzie, zwraca wskaźnik do lokalnej tablicy przydzielonej stosowi ... ach!
Zauważ również, że jeśli podasz bufor, abi::__cxa_demangle()zakłada się, że jest on przydzielony na stercie. Przydzielanie bufora na stosie jest błędem (z dokumentacji gnu): "Jeśli output_buffernie jest wystarczająco długi, jest rozszerzany za pomocą realloc." Wywołanie realloc()wskaźnika do stosu ... och! (Zobacz też miły komentarz Igora Skochinsky'ego ).

Możesz łatwo zweryfikować oba te błędy: po prostu zmniejsz rozmiar bufora w akceptowanej odpowiedzi (od 7 września 2013) z 1024 do mniejszego, na przykład 16 i nadaj mu coś o nazwie nie dłuższej niż 15 (tak realloc()jest nie wezwany). Mimo to, w zależności od systemu i optymalizacji kompilatora, wynikiem będzie: śmieci / nic / awaria programu.
Aby zweryfikować drugi błąd: ustaw rozmiar bufora na 1 i wywołaj go czymś, którego nazwa jest dłuższa niż 1 znak. Po uruchomieniu program prawie na pewno ulega awarii, gdy próbuje wywołać realloc()wskaźnik do stosu.


(Stara odpowiedź z 27 grudnia 2010)

Ważne zmiany wprowadzone w kodzie KeithB : bufor musi być przydzielony przez malloc lub określony jako NULL. NIE umieszczaj go na stosie.

Dobrze jest również sprawdzić ten status.

Nie udało mi się znaleźć HAVE_CXA_DEMANGLE. Sprawdzam __GNUG__chociaż to nie gwarantuje, że kod się skompiluje. Czy ktoś ma lepszy pomysł?

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

Zwróć uwagę, że to wymaga #include <cxxabi.h>. W przeciwnym razie działało świetnie, dzięki.
jterrace

2
From the docs : output_bufferObszar pamięci, przydzielony za pomocą malloc, o długości * bajtów, w którym jest przechowywana zdemanalizowana nazwa. Jeśli bufor_wyjściowy nie jest wystarczająco długi, jest rozszerzany za pomocą funkcji realloc. output_buffer może zamiast tego mieć wartość NULL; w takim przypadku odkodowana nazwa jest umieszczana w obszarze pamięci przydzielonym za pomocą malloc.
Igor Skochinsky

2
@IgorSkochinsky Tak, w moim poprzednim komentarzu jest literówka, ale nie mogę tego edytować. To, co chciałem napisać: „Ostatnim razem, gdy sprawdzałem, abi::__cxa_demanglespodziewałem się, że zostanie on przydzielony na stercie. ” Dziękuję bardzo za sprawdzenie tego dokumentu!
Ali

1
Należy pamiętać, że technicznie może to wyciekać, jeśli zostanie ret_valrzucone podczas budowy. Możesz użyć osłony lunety, aby się przed tym ustrzec.
GManNickG,

3
Gdyby to prawdopodobnie bardziej zrozumiałe byłoby użycie go std::unique_ptr<char, decltype(&std::free)>jako sygnatury wskaźnika.
mindvirus

29

Rdzeń wspomagający zawiera demanglera. Checkout 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>
}

Jest to w zasadzie tylko opakowanie abi::__cxa_demangle, jak sugerowano wcześniej.


1
Jeśli doładowanie jest opcją, jest to najlepsza droga!
hbobenicio

14

To jest to, czego używamy. HAVE_CXA_DEMANGLE jest ustawiane tylko wtedy, gdy jest dostępne (tylko najnowsze wersje 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
Musisz uwzględnić #include <cxxabi.h>.
fuenfundachtzig

Ciekawy. Mam __cxa_demangle bez zdefiniowanego
HAVE_CXA_DEMANGLE

@Matt Chciałem powiedzieć, że nasz system kompilacji, oparty na autoconf, ustawia HAVE_CXA_DEMANGLE tylko wtedy, gdy jest dostępny.
KeithB

19
OSTRZEŻENIE! Powyższy kod może spowodować awarię programu. Bufor musi być przydzielony przez malloc lub określony jako NULL. NIE umieszczaj go na stosie. Zobacz mój kod poniżej.
Ali


9

Tutaj spójrz na type_strings.hpp, który zawiera funkcję, która robi to, co chcesz.

Jeśli szukasz tylko narzędzia do demanglingu, którego mógłbyś np. Użyć do zmiany rzeczy wyświetlanych w pliku dziennika, spójrz na c++filt, które jest dostarczane z binutils. Może rozróżniać nazwy symboli C ++ i Java.


Warto zauważyć, że zarówno cxa_demange () (której używa kod powiązany), jak i cx ++ filt są specyficzne dla gcc. Nie ma przenośnego sposobu, aby to zrobić.
KeithB

c ++ filt nie działa, potrzebuję tego (lub większości) w czasie kompilacji, głównie przy użyciu makr.
koniec

4
Link do type_strings.cpp wygląda na uszkodzony.
StackedCrooked

1
Cześć @GregoryPakosz. Link do github w twoim powyższym komentarzu również wydaje się uszkodzony :( Pozdrawiam
olibre

Tylko ważna informacja do Twojej wiadomości: abi::__cxa_demangle()i jej podobne z <cxxabi.h> nie są specyficzne dla GCC - mogły być tylko GCC w odległej przeszłości, ale w czasie pisania tego postu <cxxabi.h>były głęboko zakorzenionym standardem ad hoc. Więc chociaż link do kodu odpowiedzi brzmiał DOI, mogę ręczyć, że Clang zapewni wsparcie najwyższej klasy w tym przypadku… qv, ze libcxxabiźródła Clanga : odpowiedni decl, impl, huge test: git.io/vRTBo , git.io/vRTBh , git.io/vRTRf - w komentarzach do kodu testu zauważono , że implementacja Clang może być w jakiś sposób bardziej wymagająca niż GCC.
fish2000

5

Jest zdefiniowana implementacja, więc nie jest to coś, co będzie przenośne. W MSVC ++ name () jest nazwą niezdekorowaną i musisz spojrzeć na raw_name (), aby uzyskać ozdobioną.
Tylko dźgnięcie w ciemność tutaj, ale pod gcc, możesz spojrzeć na demangle.h


3

Znalazłem też makro o nazwie __PRETTY_FUNCTION__, które załatwia sprawę. Daje ładną nazwę funkcji (figury :)). To jest to, czego potrzebowałem.

To znaczy daje mi następujące informacje:

virtual bool mutex::do_unlock()

Ale nie sądzę, żeby to działało na innych kompilatorach.


Tak, PRETTY_FUNCTION jest specyficzny dla GCC .
Greg Rogers

2

Nie jest to kompletne rozwiązanie, ale warto przyjrzeć się definicji niektórych standardowych (lub szeroko obsługiwanych) makr. Często zdarza się, że w kodzie logowania widać użycie makr:

__FUNCTION__
__FILE__
__LINE__

e.g.:

log(__FILE__, __LINE__, __FUNCTION__, mymessage);

4
Nie wspominając o PRETTY_FUNCTION .
CesarB

1
To da ci informacje o tym, gdzie jesteś w kodzie. Pytanie dotyczyło ładnej nazwy typu, na przykład std :: vector.
KeithB

Wspomniał, że służy do debugowania, a ja stwierdziłem, że nie jest to kompletne rozwiązanie. Inne makra, takie jak FUNCDNAME , zwrócą ozdobioną nazwę.
Łukasz

Właściwie, ponownie czytając pytanie, brzmiało: „Obecnie pracuję nad kodem do logowania, który miałby - między innymi - wypisywać informacje o funkcji wywołującej”. To działa.
Max Lybbert,

Nie jest kompletna, ponieważ nie znam przestrzeni nazw. To jest już w moim kodzie. Mimo wszystko dziękuję.
koniec

2

Niewielka odmiana rozwiązania Ali. Jeśli chcesz, aby kod nadal był bardzo podobny do

typeid(bla).name(),

pisząc to zamiast tego

Typeid(bla).name() (różniące się tylko dużą pierwszą literą)

to możesz być tym zainteresowany:

W pliku 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 pozostaje taki sam jak w rozwiązaniu Ali


1

Zobacz, __cxa_demanglektóre możesz znaleźć pod adresem cxxabi.h.


Wziąłem, jest przestarzały, zgodnie z otrzymaną wiadomością.
koniec

Gdzie znalazłeś tę wiadomość? Właśnie googlowałem i wydaje się, że jest obsługiwany, nie ma dowodów na to, że został uznany za przestarzały.
Ali

Może jest przestarzały w :: namespace. Użyj abi :: __ cxa_demangle, a nie otrzymasz ostrzeżenia. Jakiego GCC używasz?
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
OSTRZEŻENIE! Bufor musi być przydzielony przez malloc lub określony jako NULL. NIE umieszczaj go na stosie. Zobacz mój kod poniżej.
Ali

1

Przyjętego rozwiązania [1] działa w większości dobrze. Znalazłem przynajmniej jeden przypadek (i nie nazwałbym tego przypadkiem narożnym), w którym nie zgłasza tego, czego się spodziewałem ... z referencjami.

W takich przypadkach znalazłem inne rozwiązanie, zamieszczone na dole.

Przypadek problematyczny (używanie typezgodnie z definicją w [1]):

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

produkuje

Type of i is int
Type of ri is int

Rozwiązanie (używając type_name<decltype(obj)>(), zobacz kod poniżej):

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

produkuje

Type of i is int
Type of ri is int&

zgodnie z życzeniem (przynajmniej przeze mnie)

Kod . Musi znajdować się w dołączonym nagłówku, a nie w osobno skompilowanym źródle, ze względu na problemy ze specjalizacją. Zobacz na przykład niezdefiniowane odniesienie do funkcji szablonu .

#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

Zawsze chciałem użyć type_info, ale jestem pewien, że wynik funkcji składowej name () jest niestandardowy i niekoniecznie zwróci cokolwiek, co można przekonwertować na znaczący wynik.
Jeśli trzymasz się jednego kompilatora, być może istnieje funkcja specyficzna dla kompilatora, która zrobi to, co chcesz. Sprawdź dokumentację.


0

Idąc za rozwiązaniem Ali, oto alternatywa oparta na szablonie C ++ 11, która działała najlepiej w moim przypadku.

// 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();
}

Stosowanie:

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

Wydrukuje:

double                                                                        
int                                                                           
test::SomeStruct
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.