Pourquoi cette méthode C segfaulting?
-
27-09-2019 - |
Question
J'écris une classe de liste chaînée immuable en C, mais une méthode est segfaulting mystérieusement. Le code est destiné à être à peu près équivalent à ceci:
class PList(object):
def __init__(self, first, rest=None):
self.first = first
self.rest = rest
def cons(self, item):
return PList(item, self)
Voici mon code:
#include <Python.h>
#include <structmember.h>
static PyTypeObject PListType;
typedef struct PListStruct{
PyObject_HEAD
PyObject *first;
struct PListStruct *rest;
} PList;
static PyMemberDef plist_members[] = {
{"first", T_OBJECT_EX, offsetof(PList, first), READONLY, "First element"},
{"rest", T_OBJECT_EX, offsetof(PList, rest), READONLY, "Rest of the list"},
{NULL}
};
static PyObject *
PList_cons(PList *self, PyObject *arg)
{
PList *new_list = PyObject_CallFunctionObjArgs(&PListType, arg, self);
Py_INCREF(new_list);
return new_list;
}
static PyMethodDef plist_methods[] = {
{"cons", (PyCFunction)PList_cons, METH_O, "Add an item to the list"},
{NULL}
};
static void PList_dealloc(PList *self)
{
Py_XDECREF(self->first);
Py_XDECREF(self->rest);
self->ob_type->tp_free((PyObject*)self);
}
static int
PList_init(PList *self, PyObject *args, PyObject *kwds)
{
PyObject *first=NULL, *rest=NULL, *tmp;
static char *kwlist[] = {"first", "rest", NULL};
if (! PyArg_ParseTupleAndKeywords(args, kwds, "O|O", kwlist,
&first, &rest))
return -1;
if (first){
tmp = self->first;
Py_INCREF(first);
self->first = first;
Py_XDECREF(tmp);
}
if (rest) {
tmp = self->rest;
Py_INCREF(rest);
self->rest = rest;
Py_XDECREF(tmp);
}
else {
tmp = self->rest;
Py_INCREF(Py_None);
self->rest = Py_None;
Py_XDECREF(tmp);
}
return 0;
}
static PyTypeObject PListType= {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"pysistence.persistent_list.PList", /*tp_name*/
sizeof(PList), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)PList_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"Persistent list", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
plist_methods, /* tp_methods */
plist_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)PList_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
};
#ifndef PyMODINIT_FUNC
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initpersistent_list(void)
{
PyObject *m;
PListType.tp_new = PyType_GenericNew;
if (PyType_Ready(&PListType) < 0)
return;
m = Py_InitModule3("pysistence.persistent_list", 0,
"Docstring");
Py_INCREF(&PListType);
PyModule_AddObject(m, "PList", (PyObject*)&PListType);
}
Si je lance ce code, il segfaults sur la dernière ligne:
from pysistence.persistent_list import PList
p = PList(1)
p = PList(2, p)
p = p.cons(3)
Je suis sûr que je vais juste faire quelque chose de stupide, mais je ne vois pas ce qu'elle est. Y at-il quelque chose que je suis absent?
La solution
Je lis de la documentation:
PyObject* PyObject_CallFunctionObjArgs(PyObject *callable, ..., NULL)
Return value: New reference.
Appel d'un objet Python appelable appelable, avec un nombre variable de PyObject * arguments. Les arguments sont fournis sous forme d'un nombre variable de paramètres suivie par NULL. Renvoie le résultat de l'appel en cas de succès, ou NULL en cas d'échec.
Il vous manque la valeur NULL à la fin.
Edit: Ho et vous voulez également vérifier si la fonction retourne NULL en cas de défaillance de la mémoire