【问题标题】:c++ std::bad_alloc on std::filesystem::path appendc++ std::bad_alloc on std::filesystem::path append
【发布时间】:2019-11-06 09:16:59
【问题描述】:

我遇到了一种非常奇怪的行为,我将其浓缩为一个非常基本的测试:

#include <string>
#include <filesystem>

int main(void)
{
  const std::string name = "foo";
  const std::filesystem::path lock_dir = "/tmp";
  std::filesystem::path lockfile = lock_dir / name;

  return 0;
}

我用g++ -std=c++17 -Wall -Wextra -Werror -g foo.cpp -o foo 编译这个。当我运行它时,我在附加两个路径的行上得到一个 std::bad_alloc 异常。这是我使用 gdb 看到的内容

#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007ffff742c801 in __GI_abort () at abort.c:79
#2  0x00007ffff7a8e1f2 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7a99e36 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7a99e81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7a9a0b5 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x00007ffff7a907a7 in std::__throw_bad_alloc() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#7  0x0000555555558cfe in __gnu_cxx::new_allocator<std::filesystem::__cxx11::path::_Cmpt>::allocate (this=0x7fffffffe080, __n=12297828079348111650) at /usr/include/c++/8/ext/new_allocator.h:102
#8  0x00005555555587d0 in std::allocator_traits<std::allocator<std::filesystem::__cxx11::path::_Cmpt> >::allocate (__a=..., __n=12297828079348111650) at /usr/include/c++/8/bits/alloc_traits.h:436
#9  0x0000555555557f76 in std::_Vector_base<std::filesystem::__cxx11::path::_Cmpt, std::allocator<std::filesystem::__cxx11::path::_Cmpt> >::_M_allocate (this=0x7fffffffe080, __n=12297828079348111650)
    at /usr/include/c++/8/bits/stl_vector.h:296
#10 0x0000555555558387 in std::_Vector_base<std::filesystem::__cxx11::path::_Cmpt, std::allocator<std::filesystem::__cxx11::path::_Cmpt> >::_M_create_storage (this=0x7fffffffe080, __n=12297828079348111650)
    at /usr/include/c++/8/bits/stl_vector.h:311
#11 0x00005555555579cf in std::_Vector_base<std::filesystem::__cxx11::path::_Cmpt, std::allocator<std::filesystem::__cxx11::path::_Cmpt> >::_Vector_base (this=0x7fffffffe080, __n=12297828079348111650, __a=...)
    at /usr/include/c++/8/bits/stl_vector.h:260
#12 0x0000555555556d39 in std::vector<std::filesystem::__cxx11::path::_Cmpt, std::allocator<std::filesystem::__cxx11::path::_Cmpt> >::vector (this=0x7fffffffe080, 
    __x=std::vector of length -1303124922760, capacity -1303124922760 = {...}) at /usr/include/c++/8/bits/stl_vector.h:460
#13 0x000055555555635f in std::filesystem::__cxx11::path::path (this=0x7fffffffe060, Python Exception <class 'gdb.error'> There is no member or method named _M_t.: 
__p=...) at /usr/include/c++/8/bits/fs_path.h:166
#14 0x00005555555563c8 in std::filesystem:: (Python Exception <class 'gdb.error'> There is no member or method named _M_t.: 
__lhs=..., Python Exception <class 'gdb.error'> There is no member or method named _M_t.: 
__rhs=...) at /usr/include/c++/8/bits/fs_path.h:554
#15 0x0000555555555fbe in main () at foo.cpp:8

这带来了几个问题:

  1. 我的测试代码有什么问题?
  2. 为什么 GDB 在调用堆栈中使用 python 显示任何内容?

预料到这个问题,我的 g++ 是 gcc version 8.3.0 (Ubuntu 8.3.0-6ubuntu1~18.04.1),我的 gdb 是 GNU gdb (Ubuntu 8.2-0ubuntu1~18.04) 8.2

UPDATE这是成功编译的可执行文件的ldd输出

linux-vdso.so.1 (0x00007ffc697b2000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f5c35444000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f5c3522c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f5c34e3b000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f5c34a9d000)
/lib64/ld-linux-x86-64.so.2 (0x00007f5c35a2d000)

【问题讨论】:

  • 在编译/链接的时候加上-lstdc++fs有什么不同吗?
  • @TedLyngmo 没有它,他会遇到链接问题。
  • 我想知道如何在没有-lstdc++fs 的情况下使用 gcc 8.3.0 进行编译。它应该失败...:-/
  • 这里是另一个数据点。在我坏的虚拟机中,我可以用g++ -std=c++17 -Wall -Wextra -Werror -g foo.cpp -o foo 编译/链接一个坏的可执行文件。当我使用g++ -std=c++17 -Wall -Wextra -Werror -g foo.cpp -o foo -lstdc++fs 编译/链接时,它运行没有问题
  • @TedLyngmo 您上面的 cmets 是正确的,如果不是 Ubuntu 的“功能”,它将是 100% 正确的。 Ubuntu 为 libstdc++ 提供不一致的标头和共享库是设计使然,这就是程序链接时不使用-lstdc++fs 的原因,这就是它在运行时崩溃的原因。请参阅下面的答案以获取更多信息。

标签: c++ exception gdb c++17 libstdc++


【解决方案1】:

这是由 Ubuntu 的一个“特性”引起的,它提供的 libstdc++.so 比系统自带的 g++ 晚。详情请见https://bugs.launchpad.net/ubuntu/+source/gcc-8/+bug/1824721。

通常在使用 GCC 8 进行编译时,std::filesystem 符号不会出现在 libstdc++.so 中,因此如果您未能使用 link with -lstdc++fs,那么您将收到链接器错误。但是因为来自 GCC 9 的较新的libstdc++.so确实包含std::filesystem 的符号,所以不会发生链接器错误。不幸的是,文件系统符号的 GCC 9 版本与 GCC 8 标头不兼容(因为文件系统库在 GCC 8 中是实验性的且不稳定,并且 filesystem::path 的布局已针对 GCC 9 进行了更改)。这意味着您的程序链接了,但在运行时它使用了错误的符号 filesystem::path,并且发生了不好的事情。

我没有预料到这个问题,因为我不知道 Ubuntu 将旧的 libstdc++ 头文件与新的 libstdc++ 共享库混合在一起。这通常是安全的,除了 when using "experimental", incomplete features, such as the C++17 features in GCC 8。

我为 Ubuntu 建议的修复方法是让 g++ 自动将 -lstdc++fs 添加到编译命令的末尾。如果您使用任何 std::filesystem 功能,那么这些符号的正确定义应该在 GCC 8 的 libstdc++fs.a(而不是 GCC 9 的 libstdc++.so)中找到,并且在大多数情况下,一切都应该正常工作。如果 Ubuntu 尚未使用该解决方法更新其 GCC 软件包,您也可以通过确保手动链接到 -lstdc++fs 来使其正常工作(无论如何,这是 GCC 8 所要求的)。

【讨论】:

  • 哇...讨厌 :-) 很高兴知道!我在工作中运行 Ubuntu。
  • @TedLyngmo,是的,如果你忘记了-lstdc++fs,应该是一个链接器错误,它变成了未定义的行为和运行时崩溃。可恶的。根据gcc.gnu.org/bugzilla/show_bug.cgi?id=90050#c8 和bugs.launchpad.net/ubuntu/+source/gcc-8/+bug/1824721/comments/…,它现在应该已修复,所以请确保您是最新的。
  • 我的本地机器可能已经更新,但我应该检查它们在 CI 机器的容器中运行的内容,boost::filesystem 也是其中的一部分。
【解决方案2】:

我将用其他人在 cmets 中的发现总结我自己的发现。这不是一个实际的答案(还),因为此时我无法解释失败的原因。

我能够通过在常规的 ubuntu Docker 映像中安装 g++-8 和 g++-9 来重现此行为,这样我就有了 /usr/bin/g++-8 和 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.26 可用。

根据gdb 堆栈跟踪,错误发生在std::vector 构造函数中的某处。似乎在std::filesystem::path 的默认复制构造函数在其operator/ 内部调用时会发生这种情况:

/usr/include/c++/8/bits/fs_path.h

  /// Append one path to another
  inline path operator/(const path& __lhs, const path& __rhs)
  {
    path __result(__lhs);  // <-- fails here
    __result /= __rhs;
    return __result;
  }

这一发现可以进一步简化测试用例:

#include <filesystem>

int main(void)
{
  const std::filesystem::path first = "/tmp";
  const std::filesystem::path second(first);

  return 0;
}

这清楚地表明问题出在调用复制构造函数的某个地方。

std::filesystem::path 中唯一的vector 是这个向量(大概是路径分量):

/usr/include/c++/8/bits/fs_path.h

    struct _Cmpt;
    using _List = _GLIBCXX_STD_C::vector<_Cmpt>;
    _List _M_cmpts; // empty unless _M_type == _Type::_Multi

根据堆栈跟踪,当复制这个向量时,我们立即进入stl_vector.h:

/usr/include/c++/8/bits/stl_vector.h

      vector(const vector& __x)
      : _Base(__x.size(),
        _Alloc_traits::_S_select_on_copy(__x._M_get_Tp_allocator()))
      {

但是如果我们在_Vector_base的构造函数中打印__n的值:

      _Vector_base(size_t __n, const allocator_type& __a)
      : _M_impl(__a)
      { _M_create_storage(__n); }

我们会得到一些非常大的数字,这让我认为不正确的向量 __x 以某种方式传递给了复制构造函数。

现在,当你将 g++-8 与 g++-9 的库结合起来时,为什么会发生这种情况,我不知道(目前),我猜如果他们需要了解真正的原因,应该更深入一层。

但我猜你的主要问题的答案是“问题是由你的编译器和库版本之间的不兼容引起的”:)

【讨论】:

  • 看起来构造函数调用了 _M_spit_cmpts 以某种方式使向量陷入困境。
  • “这个问题是由你的编译器和库版本之间的不兼容引起的”——你是对的,问题是 OP 没有造成这种不兼容, Ubuntu 做到了 :-( 请参阅我的答案,了解缺失的部分。
  • 谢谢@JonathanWakely - 你的答案应该是被接受的:)
猜你喜欢
  • 1970-01-01
  • 2011-04-23
  • 1970-01-01
  • 2021-10-02
  • 1970-01-01
  • 2018-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多