为啥在使用 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)

为啥在使用 ssl 时会出现连接问题?

为啥使用 SingleAsync 时会出现此错误?

为啥我在使用 tkinter 时会出现这个 TypeError?