У меня есть класс 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);
}
Кто-нибудь есть какие-либо предложения о том, как я мог бы более эффективно отлаживать это, или посмотреть, что может быть проблема?
Можете ли вы воспроизвести проблему, если вы скомпилируете без ASLR (или если вы включили ASLR под gdb)? Я предполагаю, что вы используете ASLR, конечно –
Нет. Это делает его более чувствительным. Например, на этот раз я получаю '', но на самом деле это не segfault. –
Если вы видите '', вы по-прежнему испытываете неустойчивое поведение. Эта область памяти читаема, поэтому вы не получите segfault, но все же это не действительный объект Python. Это может быть хорошей отправной точкой для отладки. Кстати, я предполагаю, что для получения такого результата вы отключите ASLR, поэтому у вас больше шансов получить читаемый адрес памяти. Я прав? –