класс cython, содержащий строки c; переполнение буфера? - PullRequest
0 голосов
/ 25 марта 2019

Пытаясь выучить немного Cython, я пытался написать игрушечную библиотеку, которая просто содержит несколько строк (соответствует доступному выбору факторного / категориального типа данных). Строки, на которые указывают классы, перезаписываются, и мой C / Cython-foo слишком мал, чтобы понять, почему.

Результат примерно такой:

>>> import coupla
>>> ff = coupla.CouplaStrings(["one", "two"])
>>> ff
write, two
>>> ff
, two
>>> ff
two, two

Помощь с благодарностью! Я чувствую, что схожу с ума. Кажется, что просто использование функций to_cstring_array и to_str_list работает нормально, но внутри класса все идет как капут.

cdef extern from "Python.h":
    char* PyUnicode_AsUTF8(object unicode)

from libc.stdlib cimport malloc, free

cdef char **to_cstring_array(list_str):
    """Stolen from Stackoverflow:
    https://stackoverflow.com/questions/17511309/fast-string-array-cython/17511714#17511714
    """
    cdef Py_ssize_t num_strs = len(list_str)
    cdef char **ret = <char **>malloc(num_strs * sizeof(char *))

    for i in range(num_strs):
        ret[i] = PyUnicode_AsUTF8(list_str[i])

    return ret

cdef to_str_list(char **cstr_array, Py_ssize_t size):
    cdef int i
    result = []

    for i in range(size):
        result.append(bytes(cstr_array[i]).decode("utf-8"))

    return result

cdef class CouplaStrings:
    cdef char **_strings
    cdef Py_ssize_t _num_strings

    def __init__(self, strings):
        cdef Py_ssize_t num_strings = len(strings)
        cdef char **tstrings = <char **> to_cstring_array(strings)

        self._num_strings = num_strings
        self._strings = tstrings

    def __repr__(self):
        """Just for testing."""
        return ", ".join(to_str_list(self._strings, self._num_strings))

    def __dealloc__(self):
        free(self._strings)

Edit:

См. Ответ пользователя user2357112 ниже. Отредактированная версия CouplaStrings, похоже, позволяет избежать этой конкретной проблемы, хотя я не буду клясться в ее полной корректности.

Редактировать 2: ЭТО НЕПРАВИЛЬНО ИГНОРИРУЕТСЯ ТОЛЬКО ДЛЯ ИСТОРИЧЕСКИХ ЦЕЛЕЙ

cdef class CouplaStrings:
    cdef char **_strings
    cdef Py_ssize_t _num_strings

    def __init__(self, strings):
        cdef Py_ssize_t num_strings = len(strings)

        cdef char **ret = <char **> PyMem_Malloc(num_strings * sizeof(char *))

        for i in range(num_strings):
            ret[i] = <char *> PyMem_Realloc(PyUnicode_AsUTF8(strings[i]),
                                            sizeof(char *))

        self._num_strings = num_strings
        self._strings = ret

    def __repr__(self):
        """Just for testing."""
        return ", ".join(to_str_list(self._strings, self._num_strings))

    def __dealloc__(self):
        PyMem_Free(self._strings)

1 Ответ

1 голос
/ 25 марта 2019

Вам не удалось учесть владельца и управление памятью.

Кодировка UTF-8, возвращаемая PyUnicode_AsUTF8, принадлежит строковому объекту PyUnicode_AsUTF8, который был вызван, и он восстанавливается, когда этострока умирает.Чтобы препятствовать строковому объекту умирать раньше, чем ваш объект, ваш объект должен сохранять (Python) ссылку на строковый объект.Кроме того, вы можете скопировать кодировки UTF-8 в память, которую вы выделяете сами, и взять на себя ответственность за освобождение этой памяти самостоятельно.

В противном случае у вас просто будет массив висячих указателей.

...