Ubuntu下C++调用Cython编译的.so中Python函数遇段错误求助
问题:C++调用Cython编译的Python模块出现段错误
环境与准备工作
- 系统:Ubuntu
- Python环境:Miniconda Python3.9
- 工作目录:
~/exp,目录结构:exp ├── exp │ ├── __init__.py │ └── main.pyx ├── setup.py ├── run.cpp └── run.py
Cython模块代码(exp/main.pyx)
def add(a, b): return a+b def entry_point(): print(add(21,21))
编译脚本(setup.py)
from setuptools import setup from Cython.Build import cythonize setup( name='exp', ext_modules=cythonize("exp/main.pyx"), libraries=[('python3.9', {'include_dirs': ["~/miniconda3/include/python3.9"]})], library_dirs=['~/miniconda3/lib'] )
编译命令与验证
执行编译生成.so文件:
python3 setup.py build_ext --inplace
生成的main.cpython-39-x86_64-linux-gnu.so位于~/exp/exp目录下。
使用Python调用正常(run.py):
from exp.main import entry_point if __name__ == "__main__": entry_point()
执行后输出42,模块功能正常。
问题重现
编写run.cpp尝试调用Cython模块:
#include <iostream> #include <dlfcn.h> #include "Python.h" int main(int argc, char *argv[]) { setenv("PYTHONPATH",".",1); Py_Initialize(); PyObject *pName, *pModule, *pDict, *pFunc, *pValue, *presult; // Initialize the Python Interpreter Py_Initialize(); // Build the name object pName = PyUnicode_FromString((char*)"exp/main"); // Load the module object pModule = PyImport_Import(pName); // pDict is a borrowed reference pDict = PyModule_GetDict(pModule); // pFunc is also a borrowed reference pFunc = PyDict_GetItemString(pDict, (char*)"entry_point"); Py_DECREF(pValue); // Clean up Py_DECREF(pModule); Py_DECREF(pName); Py_Finalize(); }
编译命令:
g++ -Wall -I~/miniconda3/include/python3.9 run.cpp -L~/miniconda3/lib -lpython3.9 -o run.o -ldl
执行./run.o时出现Segmentation fault (core dumped)。
错误分析与修复步骤
1. 重复初始化Python解释器
代码中连续调用了两次Py_Initialize(),会导致Python内部状态异常,直接删除其中一次即可。
2. 未检查空指针
PyImport_Import(pName)可能因模块找不到、路径错误等返回NULL,后续直接操作pModule会触发段错误,必须添加空指针检查:
if (pModule == NULL) { PyErr_Print(); std::cerr << "Failed to import module" << std::endl; return 1; }
3. 未初始化的指针调用Py_DECREF
pValue未被赋值就调用Py_DECREF(pValue),会访问非法内存地址,直接删除这行代码。
4. 模块导入名称错误
Python模块导入应该使用点分隔符(exp.main)而非斜杠(exp/main),修正模块名称:
pName = PyUnicode_FromString("exp.main");
5. 编译命令的路径解析问题
g++参数中的~不会被shell解析,需替换为绝对路径或使用$HOME展开:
g++ -Wall -I$HOME/miniconda3/include/python3.9 run.cpp -L$HOME/miniconda3/lib -lpython3.9 -o run -ldl
(注意:输出文件名建议用run而非run.o,避免和目标文件混淆)
6. 缺少函数调用与合法性检查
找到pFunc后,需要检查它是否是可调用对象,再执行调用:
if (PyCallable_Check(pFunc)) { presult = PyObject_CallObject(pFunc, NULL); if (presult == NULL) { PyErr_Print(); } Py_XDECREF(presult); } else { std::cerr << "entry_point is not callable" << std::endl; PyErr_Print(); }
修复后的完整run.cpp代码
#include <iostream> #include <dlfcn.h> #include "Python.h" int main(int argc, char *argv[]) { setenv("PYTHONPATH", ".", 1); // 仅初始化一次Python解释器 if (!Py_Initialize()) { std::cerr << "Failed to initialize Python interpreter" << std::endl; return 1; } PyObject *pName = PyUnicode_FromString("exp.main"); if (pName == NULL) { PyErr_Print(); Py_Finalize(); return 1; } PyObject *pModule = PyImport_Import(pName); if (pModule == NULL) { PyErr_Print(); Py_DECREF(pName); Py_Finalize(); return 1; } PyObject *pDict = PyModule_GetDict(pModule); if (pDict == NULL) { PyErr_Print(); Py_DECREF(pModule); Py_DECREF(pName); Py_Finalize(); return 1; } PyObject *pFunc = PyDict_GetItemString(pDict, "entry_point"); if (pFunc != NULL && PyCallable_Check(pFunc)) { PyObject *presult = PyObject_CallObject(pFunc, NULL); if (presult == NULL) { PyErr_Print(); } Py_XDECREF(presult); } else { if (PyErr_Occurred()) { PyErr_Print(); } else { std::cerr << "Could not find callable entry_point function" << std::endl; } } // 清理资源 Py_DECREF(pModule); Py_DECREF(pName); Py_Finalize(); return 0; }
测试验证
- 重新编译C++程序:
g++ -Wall -I$HOME/miniconda3/include/python3.9 run.cpp -L$HOME/miniconda3/lib -lpython3.9 -o run -ldl
- 执行程序:
./run
此时应输出42,无段错误。
内容的提问来源于stack exchange,提问作者Guitheg
相关产品推荐
相关产品推荐

