【问题标题】: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

标签: python c++ python-2.7 python-c-api


【解决方案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 的函数中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-10
    • 2023-01-29
    • 2021-03-29
    • 2019-11-03
    • 1970-01-01
    • 2015-04-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多