传入指针而非地址至PyArg_ParseTuple引发段错误
CPython C API扩展模块中的段错误问题
在基于CPython C API开发Python扩展模块时,遇到一个奇怪的段错误问题(本人C语言仅入门水平)。
正常可运行的模块方法示例:
static PyObject *method_echo(PyObject *self, PyObject *args) { long a; if(!PyArg_ParseTuple(args, "l", &a)) { return NULL; } printf("Value of the passed variable is: %li\n", a); return PyLong_FromLong(a); }
将变量a声明为指针并传入PyArg_ParseTuple后(其余代码适配指针操作):
long *a; if(!PyArg_ParseTuple(args, "l", a)) { return NULL; }
会触发段错误。但移除return NULL语句后:
long *a; PyArg_ParseTuple(args, "l", a);
却能正常运行。即使通过printf确认return NULL分支从未执行,只要传入指针并保留该语句就会触发段错误。
环境信息
- 系统:macOS 11.6
- Python版本:python3.9
- 编译器:clang (clang-1300.0.29.30)
可复现代码
C扩展模块代码(test1_parsepointer.c)
#define PY_SSIZE_T_CLEAN #include <python3.9/Python.h> static PyObject *method_parse_ptr1(PyObject *self, PyObject *args) { long *a; if(!PyArg_ParseTuple(args, "l",a)) { printf("PROBLEM ENCOUNTERED\n"); }; printf(" ptr-v1: Value of var is: %li\n", *a); return PyLong_FromLong(*a); } static PyObject *method_parse_ptr2(PyObject *self, PyObject *args) { long *a; if(!PyArg_ParseTuple(args, "l",a)) { return NULL; }; printf(" ptr-v2: Value of var is: %li\n", *a); return PyLong_FromLong(*a); } static PyObject *method_parse_val(PyObject *self, PyObject *args) { long a; if(!PyArg_ParseTuple(args, "l",&a)) { return NULL; }; printf(" val: Value of var is: %li\n", a); return PyLong_FromLong(a); } static PyMethodDef parseptr_methods[] = { {"parse_ptr_v1", method_parse_ptr1, METH_VARARGS, "Parse as pointer, no NULL"}, {"parse_ptr_v2", method_parse_ptr2, METH_VARARGS, "Parse as pointer, with NULL"}, {"parse_val", method_parse_val, METH_VARARGS, "Parse as val, with NULL"}, {NULL, NULL, 0, NULL} }; static struct PyModuleDef parsing_ptrs = { PyModuleDef_HEAD_INIT, "test1_pptr", "Testing PyArg_ParseTuple vars as pointers", -1, parseptr_methods }; PyMODINIT_FUNC PyInit_test1_pptr(void) { return PyModule_Create(&parsing_ptrs); }
编译命令
clang -shared -undefined dynamic_lookup -o test1_parsepointer.so test1_parsepointer.c
模块引导文件(test1_pptr.py)
def __bootstrap__(): global __bootstrap__, __loader__, __file__ import sys, pkg_resources, importlib.util __file__ = pkg_resources.resource_filename(__name__, 'test1_parsepointer.so') __loader__ = None; del __bootstrap__, __loader__ spec = importlib.util.spec_from_file_location(__name__,__file__) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) __bootstrap__()
测试脚本
import test1_pptr as tppr """ Three functions in tppr should be: parse_ptr_v1(int) parse_ptr_v2(int) parse_val """ def main(): a = int(3) print("about to test parse-by-value...") tppr.parse_val(a) # runs fine print("about to test parse-by-pointer v1...") tppr.parse_ptr_v1(a) # runs fine print("about to test parse-by-pointer v2...") tppr.parse_ptr_v2(a) # segfaults if __name__ == "__main__": main()
测试结果
parse_val(按值解析,带return NULL分支)运行正常parse_ptr_v1(按指针解析,无return NULL分支)运行正常parse_ptr_v2(按指针解析,带return NULL分支)触发段错误
内容的提问来源于stack exchange,提问作者Aaron
相关产品推荐
相关产品推荐

