在 C++ 中嵌入 python:奇怪的分段错误

Posted

技术标签:

【中文标题】在 C++ 中嵌入 python:奇怪的分段错误【英文标题】:Embedding python in C++ : strange segmentation faults 【发布时间】:2015-02-03 15:32:09 【问题描述】:

(抱歉标题含糊,但它表明我对这个问题感到多么目瞪口呆)。

所以我正在从 C++ 程序运行 Python 代码,遵循此处描述的方法:https://docs.python.org/2/extending/embedding.html

这是 C++ 代码:

#include <Python.h>
#include <iostream>

int main(int argc, char *argv[])

    PyObject *pName, *pModule, *pDict, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) 
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    

    Py_SetProgramName(argv[0]);
    Py_Initialize();
    PySys_SetArgv(argc, argv); 
    PyObject *sys = PyImport_ImportModule("sys");
    PyObject *path = PyObject_GetAttrString(sys, "path");
    PyList_Append(path, PyString_FromString("."));
    pName = PyString_FromString((char*)argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) 
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc))    
            PyObject *pArgs = PyList_New(4);
            PyList_SetItem(pArgs,0,PyString_FromString("H-SAMPLE1-OH"));
            PyList_SetItem(pArgs,1,PyInt_FromLong(2));
            PyList_SetItem(pArgs,2,PyString_FromString("H-SAMPLE2-OH"));
            PyList_SetItem(pArgs,3,PyInt_FromLong(3));
            PyObject *arglist = Py_BuildValue("(O)", pArgs);
            Py_DECREF(pArgs);

            for(int run = 0; run < 2; run++)
             
                std::cout << "begin" << std::endl;

                pValue = PyObject_CallObject(pFunc, arglist);
                //Py_DECREF(arglist);
                if (pValue != NULL) 
                
                    int py_list_size = PyList_Size(pValue);
                    printf("list size = %d\n",py_list_size);
                    int sub_list_size = 0;
                    for(Py_ssize_t i = 0; i < py_list_size; ++i)
                    
                        PyObject *pList = PyList_GetItem(pValue, i);
                        sub_list_size = PyList_Size(pList);  
                        if(PyList_Check(pList))
                        
                            for(Py_ssize_t j = 0; j < sub_list_size; ++j)
                            
                                PyObject *pListItem = PyList_GetItem(pList, j);
                                double pyNumber = PyFloat_AsDouble(pListItem);
                                std::cout << "pynumber ok" << std::endl;
                                Py_DECREF(pListItem);
                                printf("Result of call: %f\n", pyNumber);
                            
                        
                        else
                        
                            printf("Not list!\n");
                          
                        Py_DECREF(pList);
                    
                    Py_DECREF(pValue);
                
                else 
                    std::cout << "Else" << std::endl;
                    Py_DECREF(pFunc);
                    Py_DECREF(pModule);
                    PyErr_Print();
                    fprintf(stderr,"Call failed\n");
                    return 1;
                
            
        
        else 
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    
    else 
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    
    Py_Finalize();
    return 0;

这里是玩具 Python 代码:

def test(a):
    print a
    return [[1.2,2.6],[4.7,5.6]]

注意 C++ 代码中的主循环,迭代变量“run”。当循环内的代码只执行一次时,它就像一个魅力。如果我尝试更多地运行它,例如只运行两次,它就会出错,我会遇到分段错误。显然,错误发生在第 61 行,尝试执行时

double pyNumber = PyFloat_AsDouble(pListItem);

我觉得这很奇怪。它在第一次执行期间工作正常,然后突然如果无法从 pListItem 正确获取某些东西(尽管它确实收到了它识别为大小为 2 的列表并且似乎正确处理所有其他 pyObject 指针)。知道发生了什么吗?

重现:

我编译如下:

g++ -L/usr/lib/python2.7/config-x86_64-linux-gnu -L/usr/lib -I/usr/include/python2.7 -o ms2pip ms2pip.c -lpthread -ldl  -lutil -lm  -lpython2.7 -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions

然后执行如下:

$ ./ms2pip python_code test
(so ./executable < python_file_without_py_extension > < function_name >)

【问题讨论】:

【参考方案1】:

我认为您的问题是 PyList_GetItem() 返回借用的引用。所以问题在于用pListpListItem 调用Py_DECREF()

PyObject *pList = PyList_GetItem(pValue, i);
// ...
if(PyList_Check(pList))

    for(Py_ssize_t j = 0; j < sub_list_size; ++j)
    
        PyObject *pListItem = PyList_GetItem(pList, j);
        double pyNumber = PyFloat_AsDouble(pListItem); // <-- Segfault in second iteration after released from first iteration.
        // ...
        Py_DECREF(pListItem); // <-- Bad, released in first iteration.
        // ...
    

//...
Py_DECREF(pList); // <-- Bad, released in first iteration.

pList 是借用的参考资料,您不负责与Py_DECREF() 一起发布。此外,pListItem 也是借用的参考。所以在第一次迭代中,你发布了pList 以及每个pListItem,这很糟糕。在第二次迭代中,您获取已发布的pList 和每个pListItem,并将它们视为仍然稳定,但事实并非如此。因为您正在访问已释放的对象,所以程序可能在涉及它们的任何函数调用中真正失败或给出错误结果(例如,PyList_Size(pList)PyList_GetItem(pList, j)PyFloat_AsDouble(pListItem)Py_DECREF(pListItem)Py_DECREF(pList))。

【讨论】:

我注释掉了一些 Py_DECREF 语句,它现在似乎工作正常!但是可以将它们注释掉吗?他们在那里不是有原因的吗?我必须承认我没有完全理解他们的角色(这段代码广泛地基于我链接的页面上可以找到的那个)但我希望如果你不使用它们,你会保留内存资源和可能会遇到内存问题,不是吗? @jerorx 阅读Objects, Types and Reference Counts(尤其是关于引用计数的部分)。总之,当从任何Py* 函数返回PyObject*(或后代)时,文档将指出它是借用引用,还是拥有(或新)引用我>。 借用 引用意味着您不拥有该对象,并且只会临时使用它。如果你想保留对它的引用,你必须Py_INCREF()它,以便你以后可以Py_DECREF()它。 @jerorx 使用 owned(或 new)引用,您拥有它,并且在完成后必须调用 Py_DECREF(),否则它内存会泄漏。 @jerorx 关于借用引用可以使用多长时间的一般规则是,它仅在包含对象未被修改且仅用于以下范围时才有效您获得了它(例如,来自PyList_GetItem() 的引用仅在再次调用PyList_* 之前有效)。 PyList_SetItem() 等一些函数会窃取你的引用。因此,如果您要 PyList_GetItem() 一个项目(借用参考),然后 PyList_SetItem() 该项目(窃取参考),你将不得不Py_INCREF()之间的项目以确保正确的引用计数。

以上是关于在 C++ 中嵌入 python:奇怪的分段错误的主要内容,如果未能解决你的问题,请参考以下文章

Omnet ++简单模块的C ++代码中python嵌入代码中的分段错误错误

C++:奇怪的分段错误

对象创建引起的 C++ 奇怪的分段错误

在 C++ 中使用向量时出现分段错误

分段错误:在 C++ 中弹出向量时出现 11

Python - C 嵌入式分段错误