c++ std::bad_alloc on std::filesystem::path append
c++ std::bad_alloc on std::filesystem::path append
我遇到了一个非常奇怪的行为,我将其归结为一个非常基本的测试:
#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
这带来了几个问题:
- 我的测试代码有什么问题?
- 为什么 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)
我将总结我自己的发现以及其他人在评论中的发现。这还不是一个真正的答案,因为此时我无法解释失败的原因。
我能够通过在常规 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 的库结合使用时,为什么会发生这种情况,我不知道(目前),我猜如果他们需要了解真正的东西,应该更深入一层原因。
但是我猜你的主要问题的答案是 "The problem is caused by an incompatibility between your compiler and library versions" :)
这是Ubuntu的一个"feature"造成的,它提供的libstdc++.so
比系统自带的g++
更晚libstdc++.so
。有关详细信息,请参阅 https://bugs.launchpad.net/ubuntu/+source/gcc-8/+bug/1824721。
通常在使用 GCC 8 编译时,std::filesystem
符号不会出现在 libstdc++.so
中,因此如果您 link with -lstdc++fs
失败,那么您将得到一个 linker 错误.但是因为来自 GCC 9 的较新 libstdc++.so
包含 std::filesystem
的符号,所以不会发生 linker 错误。不幸的是,文件系统符号的 GCC 9 版本与 GCC 8 headers 不兼容(因为文件系统库在 GCC 8 中是实验性的并且不稳定,并且 filesystem::path
的布局在 GCC 9 中发生了变化)。这意味着您的程序 links,但在运行时它使用了错误的 filesystem::path
符号,并且发生了不好的事情。
我没有预料到这个问题,因为我不知道 Ubuntu 将旧的 libstdc++ headers 与新的 libstdc++ 共享库混合在一起。这通常是安全的,except 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 软件包,您也可以通过确保手动 link 和 -lstdc++fs
使其工作(根据 GCC 8 的要求进行记录)无论如何)。
我遇到了一个非常奇怪的行为,我将其归结为一个非常基本的测试:
#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
这带来了几个问题:
- 我的测试代码有什么问题?
- 为什么 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)
我将总结我自己的发现以及其他人在评论中的发现。这还不是一个真正的答案,因为此时我无法解释失败的原因。
我能够通过在常规 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 的库结合使用时,为什么会发生这种情况,我不知道(目前),我猜如果他们需要了解真正的东西,应该更深入一层原因。
但是我猜你的主要问题的答案是 "The problem is caused by an incompatibility between your compiler and library versions" :)
这是Ubuntu的一个"feature"造成的,它提供的libstdc++.so
比系统自带的g++
更晚libstdc++.so
。有关详细信息,请参阅 https://bugs.launchpad.net/ubuntu/+source/gcc-8/+bug/1824721。
通常在使用 GCC 8 编译时,std::filesystem
符号不会出现在 libstdc++.so
中,因此如果您 link with -lstdc++fs
失败,那么您将得到一个 linker 错误.但是因为来自 GCC 9 的较新 libstdc++.so
包含 std::filesystem
的符号,所以不会发生 linker 错误。不幸的是,文件系统符号的 GCC 9 版本与 GCC 8 headers 不兼容(因为文件系统库在 GCC 8 中是实验性的并且不稳定,并且 filesystem::path
的布局在 GCC 9 中发生了变化)。这意味着您的程序 links,但在运行时它使用了错误的 filesystem::path
符号,并且发生了不好的事情。
我没有预料到这个问题,因为我不知道 Ubuntu 将旧的 libstdc++ headers 与新的 libstdc++ 共享库混合在一起。这通常是安全的,except 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 软件包,您也可以通过确保手动 link 和 -lstdc++fs
使其工作(根据 GCC 8 的要求进行记录)无论如何)。