Zig中const引用传递给C函数为何触发段错误?
Zig编写Python C扩展的段错误原因及替代实现
问题重现
正常运行的代码
const c = @cImport({ @cInclude("Python.h"); }); var module = c.PyModuleDef{ .m_name = "__name__", .m_doc = "__doc__", .m_size = -1, }; export fn PyInit_hello() [*c]c.PyObject { return c.PyModule_Create(&module); }
编译运行命令:
$ zig build-lib -dynamic -femit-bin=hello.so -I/usr/include/python3.12 -L/usr/lib -lpython3.12 -lc hello.zig $ python3.12 -c "import hello; print(hello.__doc__)" __doc__
触发段错误的代码
将module改为const并使用@constCast转换:
const c = @cImport({ @cInclude("Python.h"); }); const module = c.PyModuleDef{ .m_name = "__name__", .m_doc = "__doc__", .m_size = -1, }; export fn PyInit_hello() [*c]c.PyObject { return c.PyModule_Create(@constCast(&module)); }
编译运行结果:
$ zig build-lib -dynamic -femit-bin=hello.so -I/usr/include/python3.12 -L/usr/lib -lpython3.12 -lc hello.zig $ python3.12 -c "import hello" [1] 791627 segmentation fault (core dumped)
段错误原因
你的猜测完全正确:Zig会将全局const变量存入程序的只读内存段(比如Linux上的.rodata段)。而Python的PyModule_Create函数并非只读使用传入的PyModuleDef结构体——它内部会修改结构体中的一些字段(比如用于模块内部状态的相关成员)。当C代码试图向只读内存区域写入数据时,操作系统会触发段错误(SIGSEGV)来阻止非法写操作,这就是崩溃的直接原因。
无@constCast的内联实现可行性
你给出的内联写法存在严重问题:
const c = @cImport({ @cInclude("Python.h"); }); export fn PyInit_hello() [*c]c.PyObject { return c.PyModule_Create(@constCast(&c.PyModuleDef{ .m_name = "__name__", .m_doc = "__doc__", .m_size = -1, })); }
这里的c.PyModuleDef结构体是在函数栈帧上创建的临时对象,函数执行完毕后栈帧会被销毁,对应的内存会被释放或覆盖。但PyModule_Create会保留指向该结构体的指针并在后续Python运行时中使用,这会导致悬空指针,引发未定义行为(可能是段错误,也可能是随机内存污染)。
正确的无@constCast实现方式是在函数内部定义一个var类型的结构体实例,确保它的内存是可写的:
const c = @cImport({ @cInclude("Python.h"); }); export fn PyInit_hello() [*c]c.PyObject { var module_def = c.PyModuleDef{ .m_name = "__name__", .m_doc = "__doc__", .m_size = -1, }; return c.PyModule_Create(&module_def); }
如果Python运行时会长期持有这个结构体的指针,函数内的栈变量可能不够安全——此时更稳妥的方式是使用全局var(就像你最初的正常代码),或者在堆上分配结构体(用Zig的内存分配器或者C的malloc)。
内容的提问来源于stack exchange,提问作者Tobias Bergkvist
相关产品推荐
相关产品推荐

