C++ Clang で typeid(std::type_info) の name() を demangle する

ルー大柴みたいなタイトルだな。

typeidのname()関数はマングルされた暗号のような型名を返しやがるため、専用の関数でデマングルをかます必要がある。

一応ClangにもLLVM実装の__cxa_demangleが用意されている。

#include <cxxabi.h>
char* __cxa_demangle(const char* mangled_name,
                           char* output_buffer,
                         size_t* length,
                            int* status);
auto s = typeid(int).name(); // "i"
int status;

char* r = abi::__cxa_demangle(s, nullptr, nullptr, &status);
if (status == 0) {
  puts(r); // "int"
}

free(r);
__cxa_demanglemallocされた値を返すため、対象を己の意志によってfreeへと導く必要がある。

成功時、status0、失敗時はそれ以外の値が代入される。

 0 // 成功。mallocの結果を返す(利用後free必須)。output_buffer指定時はreallocの結果を返す場合がある
-1 // mallocに失敗。nullptrを返す
-2 // mangled_nameの解析に失敗。nullptrを返す
-3 // mangled_nameが空。output_buffer指定時にlengthが空。nullptrを返す

第二引数のoutput_bufferを利用する場合はmallocされたオブジェクトを渡す必要がある。その際オブジェクトのサイズを第三引数のlengthに渡す。

int status;
size_t length = 64;
char* output_buffer = static_cast<char*>(malloc(length));

for (auto s : {typeid(int[9]).name(), typeid(std::string).name()}) {
  char* r = abi::__cxa_demangle(s, output_buffer, &length, &status);
  if (status == 0) { printf("%zu: %s\n", strlen(r), r); }
  if (r != NULL) { output_buffer = r; }
}

free(output_buffer);

---------------- output ----------------
7: int [9]
85: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >
静的確保された配列を渡した場合は__cxa_demangle側でreallocによる拡張が行えずにクラッシュが発生する場合がある(malloc: *** error for object 0x090090090999: pointer being realloc'd was not allocated

output_bufferを空の状態で渡せば__cxa_demangle側でmallocされる。こちらのほうが処理は単純になる。

int status;
size_t length;
char *output_buffer = nullptr;

for (auto s : {typeid(int*).name(), typeid(int).name(), typeid(std::vector<int>).name()}) {
  output_buffer = abi::__cxa_demangle(s, output_buffer, &length, &status);
  if (status == 0) { printf("%p(%zu,%zu)(%s)\n", output_buffer, length, strlen(output_buffer), output_buffer); }
}

free(output_buffer);

---------------- output ----------------
0x900901a00(5,4)(int*)
0x900901a00(5,3)(int)
0x900902f10(49,48)(std::__1::vector<int, std::__1::allocator<int> >)
ちなみに今回範囲for文のinitializer_listに指定したconst char* type_info{}.name()のポインタはプログラム終了時まで有効な値となっているため問題ないが、std::string{}.c_str()等の内部ポインタはstd::string{}オブジェクトの生存期間に依存するため、注意しなければならない。
これが → std::string
こうなって → NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
こうなる → std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >

C++怖ぇえ……

広告
広告