为啥在使用 Python/C API 时会出现此段错误?
Posted
技术标签:
【中文标题】为啥在使用 Python/C API 时会出现此段错误?【英文标题】:Why am I getting this segfault when using the Python/C API?为什么在使用 Python/C API 时会出现此段错误? 【发布时间】:2016-01-25 16:57:05 【问题描述】:使用 Python/C API 在我的 C++ 代码中对 PyObject*
进行减法时遇到分段错误,我不知道为什么。我正在使用 C++ 和 Python 2.7。我正在使用新型类来实现未来的 Python 3 兼容性。
我的目标是创建一个 C++ 类 MyClass
来作为 Python 模块中定义的类的包装器。在MyClass
构造函数中,我传入Python 模块的名称,导入模块,定位类(它总是有一个预定义的名称PyClass
),然后调用该类来创建它的实例。然后我将生成的PyObject*
存储在MyClass
中以备将来使用。在MyClass
析构函数中,我decref 存储了PyObject*
以避免内存泄漏。
就定位类并创建它的实例而言,我已经验证一切正常。我什至验证了我可以在其他MyClass
方法中使用存储的PyObject*
,例如,访问PyClass
中的方法。但是,当析构函数执行 decref 时,会导致段错误。
这是我的代码示例。我还会在适当的时候在其他地方调用Py_Initialize()
和Py_Finalize()
,为了简洁起见,我省略了一些错误检查代码:
MyPythonModule.py
class PyClass:
pass
MyClass.h
class MyClass
public:
MyClass(const char* modulename);
~MyClass();
private:
void* _StoredPtr;
;
MyClass.cpp
#include <Python.h>
#include <iostream>
#include "MyClass.h"
MyClass::MyClass(const char* modulename)
_StoredPtr = NULL;
PyObject *pName = NULL, *pModule = NULL, *pAttr = NULL;
// Import the Python module.
pName = PyString_FromString(modulename);
if (pName == NULL) goto error;
pModule = PyImport_Import(pName);
if (pModule == NULL) goto error;
// Create a PyClass instance and store a pointer to it.
pAttr = PyObject_GetAttrString(pModule, "PyClass");
if (pAttr == NULL) goto error;
_StoredPtr = (void*) PyObject_CallObject(pAttr, NULL);
Py_DECREF(pAttr);
if (_StoredPtr == NULL) goto error;
error:
if (PyErr_Occurred()) PyErr_Print();
Py_XDECREF(pName);
Py_XDECREF(pModule);
return;
MyClass::~MyClass()
std::cout << "Starting destructor..." << std::endl;
Py_XDECREF((PyObject*)(_StoredPtr));
std::cout << "Destructor complete." << std::endl;
我知道我可以通过在析构函数中省略 Py_XDECREF()
来避免段错误,但我害怕导致内存泄漏,因为我不明白为什么会发生这种情况。我可以在其他MyClass
方法中成功使用_StoredPtr
似乎特别奇怪,但我不能对其进行decref。
我还尝试将导入模块的PyObject*
存储在MyClass
中并一直保存到_StoredPtr
被decrefed 之后,但_StoredPtr
decref 仍然存在段错误。我尝试注释掉 Py_DECREF(pAttr);
行,但这没有帮助。
正如我所提到的,我可以使用_StoredPtr
检索PyClass
中的方法,并且我还尝试将这些方法存储在MyClass
中并在析构函数中取消它们。当我这样做时,我可以decref _StoredPtr
,但是当我尝试decref 方法的PyObject*
时它会出现段错误。如果我使用多种方法执行此操作,则始终是导致段错误的最后一个 decref,无论我将它们按什么顺序放置。
关于这里发生了什么的任何见解?
【问题讨论】:
FWIW,CPython源代码使用goto
相当多...hg.python.org/cpython/file/2.7/Objects/listobject.c#l361
您的代码使用保留标识符,这对我来说有足够的理由不进一步查看。我也会停止在 C++ 中使用 C 风格的强制转换。也就是说,使用 Boost.Python 或类似的框架在 C++ 中编写 Python 模块,而不是解决所有再次带来的问题。
CPython 是 C 而不是 C++,@mgilson!在 C 中,您没有带有析构函数的对象,因此您不能使用 RAII 和类似的习惯用法。与 C++ 相比,您不能使用 goto
跳过构造函数,从而限制了 C++ RAII 的适用性。我并不是说goto
在 C++ 中总是不好的,使用带有自定义删除器的智能指针将是一个不错的选择。
@UlrichEckhardt -- 这是一个公平的观点。我没有花太多时间在 C++ 上,但我曾经使用过一个 C++ 类,这是别人为跟踪 Python 中的引用计数而编写的......
@erip 我正在使用 Python 文档中的示例 docs.python.org/2/c-api/intro.html#exceptions
【参考方案1】:
这对我有用
#include <Python.h>
#include <iostream>
#include "MyClass.h"
MyClass::MyClass(const char* modulename)
_StoredPtr = NULL;
PyObject *pName = NULL, *pModule = NULL, *pAttr = NULL;
// Import the Python module.
pName = PyString_FromString(modulename);
if (pName == NULL) goto error;
pModule = PyImport_Import(pName);
if (pModule == NULL) goto error;
// Create a PyClass instance and store a pointer to it.
pAttr = PyObject_GetAttrString(pModule, "PyClass");
if (pAttr == NULL) goto error;
_StoredPtr = (void*) PyObject_CallObject(pAttr, NULL);
Py_DECREF(pAttr);
if (_StoredPtr == NULL) goto error;
else
// do something with _StoredPtr
Py_XDECREF((*PyObject)_StoredPtr)
error:
if (PyErr_Occurred()) PyErr_Print();
Py_XDECREF(pName);
Py_XDECREF(pModule);
return;
MyClass::~MyClass()
我基本上将析构函数之外的 XDECREF 移到了使用 PyObject 的函数中。
【讨论】:
以上是关于为啥在使用 Python/C API 时会出现此段错误?的主要内容,如果未能解决你的问题,请参考以下文章
为啥我在使用 Google Clas-s-room API 时会收到 404 错误?
为啥我在尝试使用 reddit API 时会收到 405 错误?
为啥在使用 OpenEntityManagerInViewFilter 时会出现 LazyInitializationException? (使用 Spring Roo)