【发布时间】:2021-08-04 13:57:47
【问题描述】:
我正在开发一个使用这个 C++ matplotlib 包装器 matplotlibcpp.h 的项目。
使用这个原始头文件的最小示例是
#include "matplotlibcpp.h"
namespace plt = matplotlibcpp;
int main() {
plt::plot({1,3,2,4});
plt::show();
}
注意:分段错误似乎不依赖于上面的示例,但确实会出现在任何调用 mathplotlibcpp.h 头文件中的函数的程序中。我选择这个绘图示例是因为实际绘图会起作用,你会看到绘图,但是一旦你关闭它并且程序完成,你会得到分段错误。此外,它是项目 github 页面上的官方示例之一。
您也可以将 main 函数中的两行替换为例如plt::figure() 你仍然会得到一个工作程序和一个分段错误在执行的最后。
用python2.7编译好像没问题
g++ minimal.cpp -std=c++11 -I/usr/include/python2.7 -I/home/<user>/.local/lib/python2.7/site-packages/numpy/core/include/ -lpython2.7
$ ldd a.out
linux-vdso.so.1 (0x00007ffe1f3f7000)
libpython2.7.so.1.0 => /usr/lib/libpython2.7.so.1.0 (0x00007f8320f8f000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f8320db2000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007f8320c6d000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f8320c53000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f8320a86000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f8320a65000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f8320a5c000)
libutil.so.1 => /usr/lib/libutil.so.1 (0x00007f8320a57000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f83211c2000)
用python3.9编译好像会导致分段错误
g++ minimal.cpp -std=c++11 -I/usr/include/python3.9 -I/home/pascal/.local/lib/python3.9/site-packages/numpy/core/include/ -lpython3.9
这里./a.out 导致分段错误(核心转储)
$ ldd a.out
linux-vdso.so.1 (0x00007fff8dbc5000)
libpython3.9.so.1.0 => /usr/lib/libpython3.9.so.1.0 (0x00007f60176ec000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f601750f000)
libm.so.6 => /usr/lib/libm.so.6 (0x00007f60173ca000)
libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f60173b0000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007f60171e3000)
libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f60171c2000)
libdl.so.2 => /usr/lib/libdl.so.2 (0x00007f60171b9000)
libutil.so.1 => /usr/lib/libutil.so.1 (0x00007f60171b4000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f6017adf000)
两者都是在使用带有 g++ 版本 10.2.0 的 arch linux 的系统上编译的。
这是在他们的 git 中找到的 issue,但到目前为止,没有人提出解决方案。
现在我将问题隔离为对Py_Finalize() 的调用。对于 Python3,这调用 Py_FinalizeEx()。所以Python2和Python3是有区别的。
现在在matplotlibcpp.h文件中Py_Finalize()在解构器中被调用:
~_interpreter() {
Py_Finalize();
}
如果你把它注释掉,你就摆脱了分段错误。现在我真的被这个终结函数弄糊涂了,因为文档状态(对于 python3)
错误和警告:模块和模块中的对象的破坏是 以随机顺序完成;这可能会导致析构函数(del() 方法) 当它们依赖于其他对象(甚至函数)或模块时失败。 由 Python 加载的动态加载的扩展模块不是 卸载。 Python解释器分配的少量内存 可能无法释放(如果发现泄漏,请报告)。记忆捆绑 up 在对象之间的循环引用不会被释放。一些记忆 由扩展模块分配的可能不会被释放。一些扩展可能 如果他们的初始化例程被调用超过 一次;如果应用程序调用 Py_Initialize() 和 Py_FinalizeEx() 不止一次。
现在头文件中还有一个 Kill() 函数,它调用解构函数显式,但从未使用过。
现在,似乎只有当我们超出范围时才会调用解构函数,即它们从不使用free() 或delete。而且我认为它只是尝试释放已经释放的东西,但弄清楚它有点困难,因为我对 C Python API 非常不熟悉。
堆栈跟踪:(我希望我正确安装了 python 调试符号。不知道为什么 Qt5 小部件符号不显示。)
注意:我用-std=c++17 -Wall -g编译了下面的堆栈跟踪
另请注意,函数matplotlibcpp::detail::_interpreter::interkeeper(bool) 显式调用解构函数,请参阅kill()。我提到了这一点,因为在下面的堆栈跟踪中提到了这个函数——我不知道为什么。该函数的源代码有以下注释:
/*
For now, _interpreter is implemented as a singleton since its currently not possible to have
multiple independent embedded python interpreters without patching the python source code
or starting a separate process for each. [1]
Furthermore, many python objects expect that they are destructed in the same thread as they
were constructed. [2] So for advanced usage, a `kill()` function is provided so that library
users can manually ensure that the interpreter is constructed and destroyed within the
same thread.
1: http://bytes.com/topic/python/answers/793370-multiple-independent-python-interpreters-c-c-program
2: https://github.com/lava/matplotlib-cpp/pull/202#issue-436220256
*/
堆栈跟踪:
Thread 1 "MAIN" received signal SIGSEGV, Segmentation fault.
0x00007fffde884225 in ?? () from /usr/lib/libQt5Widgets.so.5
(gdb) bt
#0 0x00007fffde884225 in ?? () from /usr/lib/libQt5Widgets.so.5
#1 0x00007fffdf14540a in ?? () from /usr/lib/python3.9/site-packages/PyQt5/QtWidgets.abi3.so
#2 0x00007fffe2bc67eb in ?? () from /usr/lib/python3.9/site-packages/PyQt5/QtCore.abi3.so
#3 0x00007ffff7d0ea5c in cfunction_vectorcall_NOARGS (func=0x7fffe2cccb80, args=<optimized out>, nargsf=<optimized out>, kwnames=<optimized out>) at Objects/methodobject.c:485
#4 0x00007ffff7e0ca69 in atexit_callfuncs (module=<optimized out>) at ./Modules/atexitmodule.c:93
#5 0x00007ffff7c744e7 in call_py_exitfuncs (tstate=0x555555597240) at Python/pylifecycle.c:2374
#6 0x00007ffff7dfc627 in Py_FinalizeEx () at Python/pylifecycle.c:1373
#7 0x000055555555926d in matplotlibcpp::detail::_interpreter::~_interpreter (this=0x55555555e620 <matplotlibcpp::detail::_interpreter::interkeeper(bool)::ctx>,
__in_chrg=<optimized out>) at /home/pascal/test/cpp/foo/matplotlibcpp.h:288
#8 0x00007ffff76d24a7 in __run_exit_handlers () from /usr/lib/libc.so.6
#9 0x00007ffff76d264e in exit () from /usr/lib/libc.so.6
#10 0x00007ffff76bab2c in __libc_start_main () from /usr/lib/libc.so.6
#11 0x000055555555646e in _start ()
【问题讨论】:
-
"注意:上面的代码示例对于分段错误并不重要。" - 这是一个不好的问题开始方式。那么愿意提供minimal reproducible example 吗?在任何情况下,请在可执行文件上运行
ldd,以排除代码中不同 Python 二进制文件之间的混合。 -
我的意思是:任何对给定头文件进行至少一次调用的代码都会在生命周期结束时导致分段错误。我选择了一个绘图示例,因为它表明运行时在您关闭绘图之前一直有效,此时我们处于程序的末尾。我这样做是为了表明“段错误似乎真的出现在最后”。让我改写一下并添加 ldd。
-
如果您可以使用 gdb 运行代码并将崩溃的堆栈跟踪添加到您的问题中,那将会很有帮助。 (在段错误之后在 gdb 中运行
bt以获取堆栈跟踪。您可能需要下载 python 的调试符号以获取函数名称) -
@unddoch 添加了堆栈跟踪。我必须在 s.t. 上使用调试标志编译 python3.8.5。我得到了调试符号。我希望现在一切都是正确的。
标签: python c++ python-3.x python-2.7 segmentation-fault