Python C-API:PYDICT_GETITEM上的分割故障,可能的参考问题

Python C-API: segmentation fault on PyDict_GetItem, possible reference problem?

本文关键字:故障 问题 参考 分割 Python PYDICT GETITEM C-API      更新时间:2023-10-16

i在C中有一个引用词典列表。我正在编写一个函数来计算列表的两个成员之间的点产品:

PyObject *handle; // reference to a list of dictionaries
virtual float dot_product () (unsigned i, unsigned j) const {
    // dot product of handle[i] and handle[j]
    PyObject *a = (PyObject*)PyList_GetItem(handle, (Py_ssize_t)i);
    PyObject *b = (PyObject*)PyList_GetItem(handle, (Py_ssize_t)j);
    PyObject *key, *a_value;
    Py_ssize_t pos = 0;
    double dot_product = 0;
    while (PyDict_Next(a, &pos, &key, &a_value)) {
        PyObject* b_value = PyDict_GetItem(b, key);
        if (b_value != NULL){
            dot_product += PyFloat_AsDouble(a_value) * PyFloat_AsDouble(b_value);
        }
    }
    return dot_product;
}

这会导致分割故障。使用GDB进行调试,看来分割故障是由PYDICT_GETITEM(B,KEY)引起的。这让我怀疑我的参考数数有问题。

阅读了参考计数的文档后,似乎借了上述代码中的所有参考文献,因此我认为无需使用py_incref或py_decref ...但是我很容易错。我需要使用py_incref或py_decref中的上述代码中有一个位置吗?

编辑:我应该注意,我已经进行了检查以确保A和B不是零,并且还可以确保我和J不超过列表的大小。我在问题中从代码中删除了这些检查,以使其更简单。 -

检查您的返回值。如果ij分别超过handle引用的list的长度,则ab均可为NULLPyDict_GetItem 假设 dict传递的不是NULL指针,而是在不确认该假设的情况下将其删除,这将导致立即的segfault。

您的主要问题是确定如何报告错误。C 例外将适用于C ,但是除非您抓住并将其转换为Python级别异常,否则Python不会理解它。无论如何,在您弄清楚这一点之前,返回NaN表示失败:

#include <cmath>
PyObject *handle; // reference to a list of dictionaries
virtual float dot_product () (unsigned i, unsigned j) const {
    // dot product of handle[i] and handle[j]
    PyObject *key, *a_value;
    Py_ssize_t pos = 0;
    double dot_product = 0;
    // Check both indices are valid
    PyObject *a = (PyObject*)PyList_GetItem(handle, (Py_ssize_t)i);
    if (!a) return NAN;
    PyObject *b = (PyObject*)PyList_GetItem(handle, (Py_ssize_t)j);
    if (!b) return NAN;
    // Test if you actually got dicts
    if (!PyDict_Check(a) || !PyDict_Check(b)) return NAN;
    while (PyDict_Next(a, &pos, &key, &a_value)) {
        PyObject* b_value = PyDict_GetItem(b, key);
        if (b_value != NULL){
            // Check that both values are really Python floats and extract C double
            double a_val = PyFloat_AsDouble(a_value);
            if (a_val == -1.0 && PyErr_Occurred()) return NAN;
            double b_val = PyFloat_AsDouble(b_value);
            if (b_val == -1.0 && PyErr_Occurred()) return NAN;
            dot_product += a_val * b_val;
        }
    }
    return dot_product;
}