2016-01-12 2 views
2

У меня есть класс Python, который я написал на C с именем pyquat.Quat, и он включает в себя методы для умножения на другой Quat, возвращающий массив NumPy (Quat#to_matrix()) и для печати представление.Написание расширения Python в C и получение странных ошибок памяти

Иногда, когда я называю эти методы, я получаю SIGSEGV. Однако ошибка сегментации никогда не возникает, когда я запускаю программу в GDB.

Хотя я не могу проследить проблему, я обнаружил, что некоторые странные случаи перезаписи памяти (по-видимому). Например, вот мой сценарий и выход:

>>> from pyquat import Quat 
>>> z = Quat(4,3,2,1) * Quat(1,2,3,4) 
>>> z 
Quat{{-12, 6, 24, 12}} 
>>> z.normalize() 
Quat{{-0.40000000000000002, 0.20000000000000001, 0.80000000000000004, 0.40000000000000002}} 
>>> m = z.to_matrix() 
>>> m 
array([[-0.6 , 0. , 0.8 ], 
     [ 0.64, 0.6 , 0.48], 
     [-0.48, 0.8 , -0.36]]) 
>>> z 
'exc_traceback' 

Я делал много поисков, но я понятия не имею, почему это будет перезаписана вызывающему со строкой здесь.

Функция в вопросе:

static PyObject* pyquat_Quat_to_matrix(PyObject* self) { 
    npy_intp dims[2] = {3,3}; 

    pyquat_Quat* q = (pyquat_Quat*)(self); 

    PyArrayObject* ary = (PyArrayObject*)PyArray_SimpleNew(2, dims, NPY_DOUBLE); 
    double* T = (double*)ary->data; 

    T[0] = 1.0 - 2.0 * (q->v[2] * q->v[2] + q->v[1] * q->v[1]); 
    T[1] =  2.0 * (q->v[1] * q->v[0] + q->s * q->v[2]); 
    T[2] =  2.0 * (q->v[2] * q->v[0] - q->s * q->v[1]); 
    T[3] =  2.0 * (q->v[1] * q->v[0] - q->s * q->v[2]); 
    T[4] = 1.0 - 2.0 * (q->v[2] * q->v[2] + q->v[0] * q->v[0]); 
    T[5] =  2.0 * (q->v[2] * q->v[1] + q->s * q->v[0]); 
    T[6] =  2.0 * (q->v[2] * q->v[0] + q->s * q->v[1]); 
    T[7] =  2.0 * (q->v[2] * q->v[1] - q->s * q->v[0]); 
    T[8] = 1.0 - 2.0 * (q->v[1] * q->v[1] + q->v[0] * q->v[0]); 

    return PyArray_Return(ary); 
} 

и мой тип выглядит

typedef struct { 
    PyObject_HEAD 

    /* Type-specific fields go here */ 
    double s;  // scalar component 
    double v[3]; // vector components 
} pyquat_Quat; 

Я довольно уверен, что я правильно включая NumPy и создание как мой модуль и мой класс:

/* Initialize the pyquat module and add pyquat.Quat to it. */ 
PyMODINIT_FUNC initpyquat(void) { 
    PyObject* m; 

    pyquat_QuatType.tp_new = PyType_GenericNew; 
    if (PyType_Ready(&pyquat_QuatType) < 0) 
    return; 

    // Define the pyquat module. 
    m = Py_InitModule3("pyquat", pyquat_methods, 
     "Quaternion module with fast unit (right) quaternion math written in C."); 

    // Import NumPy to prevent a segfault when we call a function that uses NumPy API. 
    import_array(); 

    // Create the Quat class in the pyquat module. 
    Py_INCREF(&pyquat_QuatType); 
    PyModule_AddObject(m, "Quat", (PyObject *)&pyquat_QuatType); 
} 


static int pyquat_Quat_init(pyquat_Quat* self, PyObject* args) { 

    double scalar, vx, vy, vz; 

    if (!PyArg_ParseTuple(args, "dddd", &scalar, &vx, &vy, &vz)) 
    return -1; 

    // Read the scalar and vector components of the quaternion. 
    self->s = scalar; 
    self->v[0] = vx; 
    self->v[1] = vy; 
    self->v[2] = vz; 

    return 0; 
} 

Мой метод подачи кажется довольно типичным, но я замечаю, что этот метод также иногда использует obj для перезаписывания.

static PyObject* pyquat_Quat_repr(PyObject* obj) { 
    pyquat_Quat* self = (pyquat_Quat*)(obj); 
    return PyString_FromFormat("Quat{{\%s, \%s, \%s, \%s}}", 
          PyOS_double_to_string(self->s, 'g', 17, 0, NULL), 
          PyOS_double_to_string(self->v[0], 'g', 17, 0, NULL), 
          PyOS_double_to_string(self->v[1], 'g', 17, 0, NULL), 
          PyOS_double_to_string(self->v[2], 'g', 17, 0, NULL)); 
} 


static PyObject * pyquat_Quat_mul(PyObject* self, PyObject* arg) { 

    // Expects the one argument to be a pyquat_Quat 
    if (!PyObject_IsInstance(arg, (PyObject*)&pyquat_QuatType)) { 
    Py_DECREF(arg); 
    PyErr_SetString(PyExc_IOError, "expected quaternion"); 
    return NULL; 
    } 

    pyquat_Quat* rhs = (pyquat_Quat*)(arg); 
    pyquat_Quat* lhs = (pyquat_Quat*)(self); 
    pyquat_Quat* result = (pyquat_Quat *)Py_TYPE(self)->tp_alloc(Py_TYPE(self), 0); 

    result->s = lhs->s * rhs->s - (lhs->v[0] * rhs->v[0] + lhs->v[1] * rhs->v[1] + lhs->v[2] * rhs->v[2]); 
    result->v[0] = lhs->s * rhs->v[0] + rhs->s * lhs->v[0] - (lhs->v[1] * rhs->v[2] - lhs->v[2] * rhs->v[1]); 
    result->v[1] = lhs->s * rhs->v[1] + rhs->s * lhs->v[1] - (lhs->v[2] * rhs->v[0] - lhs->v[0] * rhs->v[2]); 
    result->v[2] = lhs->s * rhs->v[2] + rhs->s * lhs->v[2] - (lhs->v[0] * rhs->v[1] - lhs->v[1] * rhs->v[0]); 

    return (PyObject*)(result); 
} 

Это одно, о чем я беспокоюсь. Он возвращает self —, но почему функция умножения, которая также возвращает объект, также не приводит к тому, что Python будет эхо-повторять для объекта?

Редактировать: После некоторого расследования похоже, что проблема возникает, когда я вызываю обычную нормализацию. Нужно ли мне увеличивать или уменьшать счетчик ссылок здесь?

static PyObject* pyquat_Quat_inplace_normalize(PyObject* self) { 

    pyquat_Quat* q = (pyquat_Quat*)(self); 

    double q_mag = sqrt(q->s * q->s + q->v[0] * q->v[0] + q->v[1] * q->v[1] + q->v[2] * q->v[2]); 
    if (q_mag > PYQUAT_QUAT_SMALL) q_mag = 1.0/q_mag; 
    else       q_mag = 0.0; 

    q->s *= q_mag; 
    q->v[0] *= q_mag; 
    q->v[1] *= q_mag; 
    q->v[2] *= q_mag; 

    return self; 
} 

Вот функция умножения:

static PyObject * pyquat_Quat_mul(PyObject* self, PyObject* arg) { 

    // Expects the one argument to be a pyquat_Quat 
    if (!PyObject_IsInstance(arg, (PyObject*)&pyquat_QuatType)) { 
    Py_DECREF(arg); 
    PyErr_SetString(PyExc_IOError, "expected quaternion"); 
    return NULL; 
    } 

    pyquat_Quat* rhs = (pyquat_Quat*)(arg); 
    pyquat_Quat* lhs = (pyquat_Quat*)(self); 
    pyquat_Quat* result = (pyquat_Quat *)Py_TYPE(self)->tp_alloc(Py_TYPE(self), 0); 

    result->s = lhs->s * rhs->s - (lhs->v[0] * rhs->v[0] + lhs->v[1] * rhs->v[1] + lhs->v[2] * rhs->v[2]); 
    result->v[0] = lhs->s * rhs->v[0] + rhs->s * lhs->v[0] - (lhs->v[1] * rhs->v[2] - lhs->v[2] * rhs->v[1]); 
    result->v[1] = lhs->s * rhs->v[1] + rhs->s * lhs->v[1] - (lhs->v[2] * rhs->v[0] - lhs->v[0] * rhs->v[2]); 
    result->v[2] = lhs->s * rhs->v[2] + rhs->s * lhs->v[2] - (lhs->v[0] * rhs->v[1] - lhs->v[1] * rhs->v[0]); 

    return (PyObject*)(result); 
} 

Кто-нибудь есть какие-либо предложения о том, как я мог бы более эффективно отлаживать это, или посмотреть, что может быть проблема?

+0

Можете ли вы воспроизвести проблему, если вы скомпилируете без ASLR (или если вы включили ASLR под gdb)? Я предполагаю, что вы используете ASLR, конечно –

+0

Нет. Это делает его более чувствительным. Например, на этот раз я получаю '', но на самом деле это не segfault. –

+0

Если вы видите '', вы по-прежнему испытываете неустойчивое поведение. Эта область памяти читаема, поэтому вы не получите segfault, но все же это не действительный объект Python. Это может быть хорошей отправной точкой для отладки. Кстати, я предполагаю, что для получения такого результата вы отключите ASLR, поэтому у вас больше шансов получить читаемый адрес памяти. Я прав? –

ответ

0

Хорошо, я думаю, что нашел решение.

Проблема в моих функциях на месте, которые также возвращают self.

Например,

static PyObject* pyquat_Quat_inplace_normalize(PyObject* self) { 

    pyquat_Quat* q = (pyquat_Quat*)(self); 

    double q_mag = sqrt(q->s * q->s + q->v[0] * q->v[0] + q->v[1] * q->v[1] + q->v[2] * q->v[2]); 
    if (q_mag > PYQUAT_QUAT_SMALL) q_mag = 1.0/q_mag; 
    else       q_mag = 0.0; 

    q->s *= q_mag; 
    q->v[0] *= q_mag; 
    q->v[1] *= q_mag; 
    q->v[2] *= q_mag; 

    Py_INCREF(self); # THIS 

    return self; 
} 

мне пришлось добавить Py_INCREF(self) так self является как аргумент и возвращаемое значение.

Это, как представляется, устраняет проблему.

0

Вы сказали, что некоторые значения, прежде чем возвращать их из ваших методов, проблема исчезнет.

Вдохновленный вашим открытием, я нашел проблему:

static PyObject * pyquat_Quat_mul(PyObject* self, PyObject* arg) { 
    // Expects the one argument to be a pyquat_Quat 
    if (!PyObject_IsInstance(arg, (PyObject*)&pyquat_QuatType)) { 
    Py_DECREF(arg); 

Вы Py_DECREF аргументы ING. Вы не должны этого делать! Refcounts следует увеличивать и уменьшать только тогда, когда объекты где-то хранятся.

+0

Охх. Хорошо. Это самый ясный самый краткий ответ, который я видел до сих пор. Вы хотите включить мой ответ в свой, и я помету ваше, как принято? (Спасибо!) –

+0

@ Dr.JohnnyMohawk: я включил ваш ответ в свою? –

+1

@ Dr.JohnnyMohawk: на самом деле то, что я написал, неточно. Ваш 'Py_INCREF' из вашего ответа действительно необходим, потому что вы на самом деле возвращаете ему новую ссылку. Тем не менее, вы не должны использовать аргументы 'Py_DECREF'. Завтра я пишу более ясный и подробный ответ, время спать! :) –