第二次调用PyObject_CallObject触发Segmentation Fault问题求助
问题根源与修复方案
我帮你排查出几个关键问题,正是这些导致了第二次调用时的段错误:
1. 值传递导致Python解释器提前终止
你的task函数参数是PyRecognitionContext pr——这是值传递,会触发默认拷贝构造函数,把原对象的所有指针(module、func等)直接复制给局部的pr对象。当第一个task执行完毕时,局部的pr会调用析构函数~PyRecognitionContext(),里面的Py_Finalize()会直接关闭Python解释器!这时候原对象里的所有PyObject指针都变成了无效的野指针,第二次调用RunModel时,操作的是已经被销毁的Python环境,必然触发段错误。
修复方式:把task的参数改成引用传递,同时禁用对象的拷贝构造和赋值运算符,从根源避免意外拷贝:
// 修改task函数参数为引用 int task(PyRecognitionContext& pr, string fileName) { // ... 原有代码不变 } // 在PyRecognitionContext类中添加禁用拷贝的声明 PyRecognitionContext(const PyRecognitionContext&) = delete; PyRecognitionContext& operator=(const PyRecognitionContext&) = delete;
2. Python对象引用计数管理混乱
你的代码多处没有正确管理Python对象的引用计数,不仅会导致内存泄漏,还可能间接引发对象被提前回收:
LoadModel中,第一次获取的load函数对象func,在重新赋值为run函数时,没有释放原对象;args在LoadModel和RunModel中被重复赋值,旧的args对象没有被释放;PyString_FromString、PyByteArray_FromStringAndSize创建的临时对象,没有调用Py_DECREF释放;RunModel中返回的PyString_AsString(result)是Python内部的字符串指针,一旦result被Python GC回收,这个指针就会变成野指针。
修复方式:
- 每次创建PyObject后,在不需要时调用
Py_DECREF释放; - 返回字符串时,拷贝一份到C++的堆内存中再返回,避免依赖Python内部内存。
3. 其他潜在问题
- 多线程场景下(你注释掉的代码),需要手动管理Python的GIL(全局解释器锁),否则会引发线程安全问题;
- 缺少错误检查,比如
PyObject_GetAttrString、PyObject_CallObject可能返回NULL,需要捕获并处理这些错误。
修改后的完整代码
#include <Python.h> #include <fstream> #include <iostream> #include <stdexcept> #include <thread> #include <cstring> using namespace std; class PyRecognitionContext { PyObject *module, *func, *result, *args; public: PyRecognitionContext(int argc, char **argv) : module(nullptr), func(nullptr), result(nullptr), args(nullptr) { Py_Initialize(); PySys_SetArgv(argc, argv); PyObject* moduleName = PyString_FromString("model.t"); module = PyImport_Import(moduleName); Py_DECREF(moduleName); // 释放临时创建的模块名对象 if (module == NULL) { PyErr_Print(); throw std::invalid_argument("fails to import the module"); } } ~PyRecognitionContext() { // 按顺序释放所有持有的Python对象 if (func) Py_DECREF(func); if (args) Py_DECREF(args); if (result) Py_DECREF(result); if (module) Py_DECREF(module); Py_Finalize(); } // 禁用拷贝构造和赋值运算符,防止对象拷贝 PyRecognitionContext(const PyRecognitionContext&) = delete; PyRecognitionContext& operator=(const PyRecognitionContext&) = delete; void LoadModel(std::string model) { // 先释放之前的func(如果存在) if (func) Py_DECREF(func); func = PyObject_GetAttrString(module, "load"); if (!func) { PyErr_Print(); throw runtime_error("Failed to get 'load' function"); } // 准备调用load函数的参数 if (args) Py_DECREF(args); args = PyTuple_New(1); PyObject* modelStr = PyString_FromString(model.c_str()); PyTuple_SetItem(args, 0, modelStr); // PyTuple_SetItem会接管对象引用,无需手动DECREF PyObject* loadResult = PyObject_CallObject(func, args); if (!loadResult) { PyErr_Print(); throw runtime_error("Failed to call 'load' function"); } Py_DECREF(loadResult); // 释放load函数的返回值 // 切换到run函数 Py_DECREF(func); func = PyObject_GetAttrString(module, "run"); if (!func) { PyErr_Print(); throw runtime_error("Failed to get 'run' function"); } } char* RunModel(const char *imageBytes, size_t size) { if (!func) { throw runtime_error("Model not loaded, call LoadModel first"); } // 准备调用run函数的参数 if (args) Py_DECREF(args); args = PyTuple_New(1); PyObject* byteArray = PyByteArray_FromStringAndSize(imageBytes, size); PyTuple_SetItem(args, 0, byteArray); // 接管对象引用 result = PyObject_CallObject(func, args); if (!result) { PyErr_Print(); throw runtime_error("Failed to call 'run' function"); } // 拷贝Python字符串到C++堆内存,避免野指针 const char* pyStr = PyString_AsString(result); if (!pyStr) { Py_DECREF(result); throw runtime_error("Result is not a valid string"); } char* resultStr = new char[strlen(pyStr) + 1]; strcpy(resultStr, pyStr); Py_DECREF(result); // 释放Python的result对象 return resultStr; } }; int task(PyRecognitionContext& pr, string fileName) { cout << "//"; cout << fileName << endl; std::ifstream ifs(fileName, ios::binary|ios::ate); if (!ifs.is_open()) { cerr << "Failed to open file: " << fileName << endl; return -1; } size_t imgSize = ifs.tellg(); char *imageData = new char[imgSize]; ifs.seekg(0, ios::beg); ifs.read(imageData, imgSize); ifs.close(); char* result = pr.RunModel(imageData, imgSize); std::cout << result << endl; delete[] imageData; delete[] result; // 释放RunModel返回的堆内存 return 0; } int main(int argc, char *argv[]) { try { PyRecognitionContext pr(argc, argv); pr.LoadModel("/home/dickzhou/data/sex_256/cp/"); cout << "//loaded" << endl; task(pr, "/home/dickzhou/data/face/male/24-28/24/135.JPG"); task(pr, "/home/dickzhou/data/face/female/24-28/24/138.JPG"); // 多线程场景需要手动管理GIL // thread t1([&pr](){ // PyGILState_STATE gstate = PyGILState_Ensure(); // task(pr, "/home/dickzhou/data/face/male/24-28/24/135.JPG"); // PyGILState_Release(gstate); // }); // thread t2([&pr](){ // PyGILState_STATE gstate = PyGILState_Ensure(); // task(pr, "/home/dickzhou/data/face/female/24-28/24/125.JPG"); // PyGILState_Release(gstate); // }); // t1.join(); // t2.join(); cout << "//done" << endl; } catch (const exception& e) { cerr << "Error: " << e.what() << endl; return 1; } return 0; }
简化后的编译命令
g++ -I/usr/include/python2.7 -std=c++11 -g -Wall -Wextra -lpthread -lpython2.7 pdr.cpp -o pdr
内容的提问来源于stack exchange,提问作者DickRD
相关产品推荐
相关产品推荐

