[英]PyArg_ParseTuple causes segfault when passing pointer instead of address
While learning Python C extension modules utilizing CPython's C API, I have encountered a curious segfault bug (disclaimer: I have only a passing fluency of C).在学习使用 CPython 的 C API 的 Python C 扩展模块时,我遇到了一个奇怪的段错误(免责声明:我对 C 的流利度还算过得去)。 A typical example module method, written in C (which can then be imported into python) might look like this:
一个典型的示例模块方法,用 C 编写(然后可以导入到 python 中)可能如下所示:
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);
}
This works for me without issue.这对我有用没有问题。 The problem comes if I choose to declare
a
as a pointer and pass it to PyArg_ParseTuple
, for example, changing the relevant lines to:如果我选择将
a
声明为指针并将其传递给PyArg_ParseTuple
,例如,将相关行更改为:
long *a;
if(!PyArg_ParseTuple(args, "l", a)) {
return NULL;
}
(and of course modifying the remaining lines to work with a pointer), this results in a segfault. (当然还有修改剩余的行以使用指针),这会导致段错误。 HOWEVER, if I remove the
return NULL
line:但是,如果我删除
return NULL
行:
long *a;
PyArg_ParseTuple(args, "l", a);
This runs without issue.这运行没有问题。 Even though the
return NULL
statement never gets executed (I have checked that explicitly with a printf
in the conditional block), somehow it causes a segfault if I pass a pointer to PyArg_ParseTuple
.即使
return NULL
语句永远不会被执行(我已经用条件块中的printf
明确地检查过),但如果我将指针传递给PyArg_ParseTuple
,它会以某种方式导致段错误。 Any ideas what's going on?任何想法发生了什么?
Here are some details of my system, followed by some example code that should be able to reproduce the problem:下面是我的系统的一些细节,后面是一些应该能够重现问题的示例代码:
macOS 11.6 python3.9 C compiler: clang (clang-1300.0.29.30) macOS 11.6 python3.9 C 编译器:clang (clang-1300.0.29.30)
C extension module (which will import in python as test1_pptr
): C 扩展模块(将在 python 中作为
test1_pptr
导入):
test1_parsepointer.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);
}
I compile this with the following command:我使用以下命令编译它:
clang -shared -undefined dynamic_lookup -o test1_parsepointer.so test1_parsepointer.c
Create a .py
file that bootstraps this module upon import:创建一个在导入时引导此模块的
.py
文件:
test1_pptr.py: 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__()
And finally, the methods can be tested with the following python script:最后,可以使用以下 python 脚本测试这些方法:
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()
long *a;
This doesn't point to anything valid because you haven't initialized it (either by allocating memory for a long
or taking the address of an existing long
).这并不指向任何有效的东西,因为您尚未对其进行初始化(通过为
long
分配内存或获取现有long
的地址)。
if(!PyArg_ParseTuple(args, "l", a))
This is attempting to write into whatever a
points to.这是试图写入
a
指向的任何内容。 But a
doesn't point to a valid long
.但是
a
不指向有效的long
。 Therefore it crashes.因此它崩溃了。
The fact that it seems to work in some cases is completely uninteresting.它似乎在某些情况下有效的事实是完全无趣的。 Writing into an invalid pointer is undefined behaviour.
写入无效指针是未定义的行为。 Practically it's just arbitrary what
a
gets initialized to point at.实际上,它只是任意
a
被初始化指向。 There's no value in attempting to understand it.试图理解它没有任何价值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.