Хранение небезопасного C-производного временной ссылки на Python - PullRequest
0 голосов
/ 01 декабря 2018

Рассмотрим следующую придуманную функцию Cython для присоединения к списку строк:

# cython: language_level=3
cpdef test_join():
    """ ["abc", "def", "ghi"] -> "abcdefghi" """
    cdef:
        list lines = ["abc", "def", "ghi"]
        char* out = ""
        char* line = ""
        int i
    for i in range(len(lines)):
        line = lines[i]
        out = out + line
    return out

Не удастся скомпилировать с этой ошибкой:

Хранение небезопасного производного C от временныхСсылка на Python

Я предполагаю, что это связано с line, имеющим тип char* и постоянно переназначаемым.Я видел ответ на подобный вопрос , но не смог изменить этот ответ для этого базового примера.(И это также включает в себя большое количество C-API, с которым я не знаком.)

Как я могу изменить вышеуказанную функцию для компиляции и возврата в соответствии с ожиданиями?


В более широком смысле, я хочу лучше понять эту ошибку.Коммит 37e4a20 имеет несколько объяснений:

Получение char* из временного строкового объекта Python ... Ошибка времени компиляции возникает, только когда такой указатель назначенпеременной и, следовательно, превысит время жизни самой строки.


Обновление : чтобы еще больше упростить ситуацию, похоже, что это назначение вызываетПроблема:

cpdef int will_succeed():
    cdef char* a = b"hello"
    cdef char* b = b" world"
    print(a + b)  # no new assignment
    return 1

cpdef will_fail():
    cdef char* a = b"hello"
    cdef char* b = b" world"
    a = a + b  # won't compile
    return a

Я подозреваю, что может быть более правильный способ сделать это с помощью чего-то из string.pxd / string.h, но я довольно слаб в управлении памятью и эффективности C:

from libc.string cimport strcat, strcpy

cpdef use_strcat():
    cdef char out[1024]
    strcpy(out, b"")

    cdef char* a = b"hello"
    cdef char* b = b" world"

    strcat(out, a)
    strcat(out, b)
    return out

1 Ответ

0 голосов
/ 01 декабря 2018

Я думаю, что проблема в

out = out + line

Cython не определяет оператор + для строк C.Вместо этого он преобразует их в строки Python и объединяет их:

tmp1 = str(out)
tmp2 = str(line)
tmp3 = tmp1 + tmp2
out = get_c_string_from(tmp3)

out, поэтому становится недействительным указателем, как только tmp3 уничтожается (что происходит мгновенно).


Я бы не стал использовать strcat, потому что не очень эффективен для многократного использования .Вместо этого следите за текущей длиной строки и копируйте данные самостоятельно.Учитывая, что у вас есть неизвестная длина, вы, вероятно, захотите выделить строку с помощью malloc (в этом случае вы несете ответственность за ее освобождение)

from libc.stdlib cimport free, malloc, realloc
from libc.string cimport memcpy

from cython import Py_ssize_t

cdef char         *line
cdef Py_ssize_t   i
cdef Py_ssize_t   length = 0
cdef Py_ssize_t   incrlength
cdef char         *out = <char *>malloc(1)  # Reallocate as needed

try:
    out[0] = b'\x00' # keep C-strings null-terminated
    for i in range(len(lines)):
        line = lines[i]
        incrlength = len(line)
        out = <char *>realloc(out, length + incrlength + 1)
        memcpy(out + length, line, incrlength)
        length += incrlength
        out[length] = '\x00'  # keep C-strings null-terminated
    return out  # autoconversion back to a Python string

finally:
   free(out)

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

...