【问题标题】:How to demangle std::string as std::string如何将 std::string 分解为 std::string
【发布时间】:2014-08-03 10:41:59
【问题描述】:

一段时间以来,我一直在使用一些解构代码来帮助进行一些调试,而无需编写数千行动态强制转换或实现返回类名的虚函数。

template <class CLASS>
std::string getClassName(CLASS &theObject)
{
    int status = 0;

    // Convert real name to readable string
    char *realName = abi::__cxa_demangle(typeid(theObject).name(), nullptr,
                                         nullptr, &status);
    ASSERT(status == 0); // Assert for success
    VERIFY(realName, return std::string());
    // Return as string + prevent memory leaks!
    const std::string result(realName);
    free(realName);
    return result;
}

这段代码背后的想法很简单,输出我们实际使用的类。 虽然在切换到 Ubuntu 14.04 后,我不再能够使用 clang 和 c++-11/c++-14 标准进行编译,所以我已经切换到使用 libc++ 而不是 libstdc++。

切换到 libc++ 后,我注意到当我对 'std::string' 进行 demangle 时,它​​不再输出 'std::string',而是输出:

std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >

当然这是正确的,因为 std::string 是 std::basic_string 的 typedef。 尽管据我所见,在 libc++ 和 libstdc++ 中,这都是使用 typedef 以相同的方式定义的。所以我真的不明白为什么切换到 libc++ 会改变这种解构。

有人知道为什么这是不同的,以及如果 CLASS 是“std::string”,如何获得“std::string”,如果 CLASS 是“myTemplate”,如何获得“myTemplate”?

Tnx 提前!

JVApen

【问题讨论】:

  • 阅读 C++ itanium ABI 文档。 std::basic_string 有一个特殊的名称修饰,但std::__1::basic_string 没有。您将不得不使用一些后处理。
  • 据我所知,Clang 标准库实现将东西放在 __1 命名空间(我想支持版本控制,然后命名空间通过内联命名空间加入 std)。由于他们使用的是 GCC 的 demangler,因此该 demangler 不会处理 LLVM/Clang 命名约定。
  • 我有 /usr/include/c++/v1/cxxabi.h,如果我理解正确的话,它是 libc++ 版本,而不是 libstdc++ 版本。因此,要么它以某种方式与错误的库链接,要么 libc++-abi 无法破解自己的结构

标签: c++ string c++11 libc++ demangler


【解决方案1】:

libc++ 使用 inline namespaces 对其 ABI 进行版本控制。它当前使用的内联命名空间是std::__1。这样做是为了让 Apple 可以同时发布 gcc libstdc++ 和 libc++。 Dylib A 可能链接到 libstdc++,dylib B 可能链接到 libc++,应用程序可能链接到两个 dylib。发生这种情况时,您不希望意外地将 libstdc++ std::string 与 libc++ std::string 混淆。

它们具有相同的 API,因此很容易通过跨 dylib 边界传递 std::string 来意外地做到这一点。解决方案是告诉编译器以不同的方式处理它们,而这正是内联命名空间所做的(并且是为此而发明的)。现在,如果它们在应用程序中意外混合,将导致链接时间错误,因为链接器看到两种不同的类型,这可以从它们不同的重命名名称中得到证明。

拆解器的工作只是简单地告诉您真相:您输入的符号的拆解名称是什么。它运行良好。

确实存在一种方法可以在 libc++ 中关闭 ABI 版本控制。在 <__config> 中搜索 _LIBCPP_BEGIN_NAMESPACE_STD_LIBCPP_END_NAMESPACE_STD。您可以看到一些平台如何定义它以打开内联命名空间,而有些则没有。这是一个非常大的锤子,可以用来解决您的问题。如果您以这种方式更改 libc++ 的 ABI,则必须重新构建在您的平台上编译和链接到 libc++ 的所有内容。

这是我有时会使用的针对您的问题的更简单的部分解决方案:

#include <iostream>
#include <type_traits>
#include <memory>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <cxxabi.h>

namespace
{

inline
void
filter(std::string& r, const char* b)
{
    const char* e = b;
    for (; *e; ++e)
        ;
    const char* pb = "std::__1::";
    const int pl = std::strlen(pb);
    const char* pe = pb + pl;
    while (true)
    {
        const char* x = std::search(b, e, pb, pe);
        r.append(b, x);
        if (x == e)
            break;
        r += "std::";
        b = x + pl;
    }
}

}  // unnamed namespace

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

这只是在将其呈现给您之前过滤掉损坏名称中的::__1。如果您愿意,也可以使用相同的技术将std::__1::basic_string&lt;char, std::__1::char_traits&lt;char&gt;, std::__1::allocator&lt;char&gt; &gt; 转换为std::string

Itanium ABI 只有少数与这样的 typedef 对应的“压缩”。它们是std::stringstd::istreamstd::ostreamstd::iostream

【讨论】:

    猜你喜欢
    • 2016-01-28
    • 2016-01-06
    • 1970-01-01
    • 2020-11-04
    • 1970-01-01
    相关资源
    最近更新 更多