You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python C扩展类型__reduce__方法声明触发段错误排查

问题:Python C扩展类型__reduce__方法触发段错误

我给自己实现的Python C扩展类型编写了__reduce__方法以支持pickle序列化,该操作在其他类型上已成功实现,但这个类型却触发了段错误。以下是最小复现代码、GDB调试输出与回溯信息,以及环境信息:

最小复现代码

main.c

#define PY_SSIZE_T_CLEAN
#include <Python.h>

typedef struct
{
    PyObject_HEAD unsigned char attr1;
    unsigned char attr2;
    unsigned char attr3;
} SomeObject;

static int SomeObject__init(SomeObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"attr1", "attr2", "attr3", NULL};
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "bbb", kwlist, &self->attr1,
                                     &self->attr2, &self->attr3))
    {
        return -1;
    }

    return 0;
}

static PyObject *SomeObject__reduce(SomeObject *self, PyObject *Py_UNUSED(ignored))
{
    return Py_BuildValue("O(BBB)N", Py_TYPE(self), self->attr1, self->attr2, self->attr3,
                         Py_None);
}

static PyMethodDef SomeObject__methods[] = {
    {
        .ml_name = "__reduce__",
        .ml_meth = (PyCFunction)SomeObject__reduce,
        .ml_flags = METH_NOARGS,
    },
};

static PyTypeObject SomeType = {
    PyVarObject_HEAD_INIT(NULL, 0)
        .tp_name = "somemodule.SomeObject",
    .tp_basicsize = sizeof(SomeObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_methods = SomeObject__methods,
    .tp_init = (initproc)SomeObject__init,
    .tp_new = PyType_GenericNew,
};

static PyModuleDef somemodule = {
    PyModuleDef_HEAD_INIT,
    .m_name = "somemodule",
    .m_size = -1,
};

PyMODINIT_FUNC
PyInit_somemodule(void)
{
    if (PyType_Ready(&SomeType) < 0)
        return NULL;

    PyObject *m = PyModule_Create(&somemodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&SomeType);
    if (PyModule_AddObject(m, "Some", (PyObject *)&SomeType) < 0)
    {
        Py_DECREF(&SomeType);
        Py_DECREF(m);
        return NULL;
    }

    return m;
}

setup.py

from setuptools import setup, Extension

setup(
    name="somemodule",
    version="0.1.0",
    ext_modules=[Extension("somemodule", ["main.c"])]
)

demo.py

import somemodule
somemodule.Some(12, 12, 12).__reduce__()

GDB调试输出

$ gdb /home/xlurio/Playground/minimum-SIGSEGV/venv/bin/python -ex "run \"/home/xlurio/Playground/minimum-SIGSEGV/demo.py\""
Program received signal SIGSEGV, Segmentation fault.
0x000000000058da02 in PyUnicode_FromFormatV ()
(gdb) bt
#0  0x000000000058da02 in PyUnicode_FromFormatV ()
#1  0x000000000055cbaf in PyErr_Format ()
#2  0x00000000004d2924 in ?? ()
#3  0x00007ffff7fbe252 in PyInit_somemodule () at main.c:57
#4  0x0000000000685b4e in _PyImport_LoadDynamicModuleWithSpec ()
#5  0x0000000000686661 in ?? ()
#6  0x00000000005c52f0 in ?? ()
#7  0x00000000005f61c8 in PyVectorcall_Call ()
--Type <RET> for more, q to quit, c to continue without paging--
#8  0x0000000000571917 in _PyEval_EvalFrameDefault ()
#9  0x0000000000569cea in _PyEval_EvalCodeWithName ()
#10 0x00000000005f6a13 in _PyFunction_Vectorcall ()
#11 0x0000000000570ac2 in _PyEval_EvalFrameDefault ()
#12 0x00000000005f6836 in _PyFunction_Vectorcall ()
#13 0x000000000056bbdf in _PyEval_EvalFrameDefault ()
#14 0x00000000005f6836 in _PyFunction_Vectorcall ()
#15 0x000000000056b9fd in _PyEval_EvalFrameDefault ()
--Type <RET> for more, q to quit, c to continue without paging--
#16 0x00000000005f6836 in _PyFunction_Vectorcall ()
#17 0x000000000056b9fd in _PyEval_EvalFrameDefault ()
#18 0x00000000005f6836 in _PyFunction_Vectorcall ()
#19 0x000000000056b9fd in _PyEval_EvalFrameDefault ()
#20 0x00000000005f6836 in _PyFunction_Vectorcall ()
#21 0x00000000005f3c41 in ?? ()
#22 0x00000000005f40a8 in _PyObject_CallMethodIdObjArgs ()
#23 0x0000000000552f1c in PyImport_ImportModuleLevelObject ()
--Type <RET> for more, q to quit, c to continue without paging--
#24 0x000000000056ddd5 in _PyEval_EvalFrameDefault ()
#25 0x0000000000569cea in _PyEval_EvalCodeWithName ()
#26 0x000000000068e7b7 in PyEval_EvalCode ()
#27 0x0000000000680001 in ?? ()
#28 0x000000000068007f in ?? ()
#29 0x0000000000680121 in ?? ()
#30 0x0000000000680db7 in PyRun_SimpleFileExFlags ()
#31 0x00000000006b8122 in Py_RunMain ()
--Type <RET> for more, q to quit, c to continue without paging--
#32 0x00000000006b84ad in Py_BytesMain ()
#33 0x00007ffff7de4083 in __libc_start_main (main=0x4ef1e0 <main>, argc=2, argv=0x7fffffffd998, init=<optimized out>, 
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd988) at ../csu/libc-start.c:308
#34 0x00000000005fb39e in _start ()

环境信息

  • CPython版本:3.8
  • 操作系统与架构:Ubuntu 20.04 LTS,x86-64

问题分析与解决方案

核心问题

段错误的根源在于**__reduce__方法的签名与PyMethodDef中指定的调用标志不匹配**:

  • 你在SomeObject__methods中给__reduce__方法设置了METH_NOARGS标志,该标志表示方法不接受任何参数,对应的函数签名应为PyObject* func(PyObject* self)。
  • 但你的SomeObject__reduce函数定义了两个参数(SomeObject *self和PyObject *ignored),当CPython调用该方法时只会传递self一个参数,导致函数读取栈上的非法内存,最终触发段错误。

修复步骤

有两种可行的修复方式:

方式一:修改函数签名,移除多余参数

将SomeObject__reduce的签名改为单参数形式:

static PyObject *SomeObject__reduce(SomeObject *self)
{
    return Py_BuildValue("O(BBB)N", Py_TYPE(self), self->attr1, self->attr2, self->attr3,
                         Py_None);
}

同时保持PyMethodDef中的METH_NOARGS标志不变。

方式二:调整调用标志为METH_O

如果需要保留第二个参数(即使未使用),将PyMethodDef中的ml_flags改为METH_O:

static PyMethodDef SomeObject__methods[] = {
    {
        .ml_name = "__reduce__",
        .ml_meth = (PyCFunction)SomeObject__reduce,
        .ml_flags = METH_O, // 修改此处
    },
};

METH_O标志表示方法接受一个可选参数,与你当前的函数签名匹配。

验证

修复后,重新编译扩展并运行demo.py,__reduce__方法将正常返回预期的元组,不会再触发段错误。

内容的提问来源于stack exchange,提问作者Kalegas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 08:59:54