【问题标题】:Storing unsafe C derivative of temporary Python reference存储临时 Python 引用的不安全 C 派生
【发布时间】:2018-12-01 17:46:05
【问题描述】:

考虑以下人为的 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

编译失败,报错:

存储临时 Python 引用的不安全 C 派生词

我假设这与 line 的类型为 char* 并不断重新分配有关。我已经看到了similar question 的答案,但无法针对这个基本示例修改该答案。 (而且它还涉及大量我不熟悉的 C-API。)

如何修改上面的函数才能按预期编译返回?


更广泛地说,我想更好地理解这个错误。 commit37e4a20有一点解释:

从临时 Python 字符串对象中获取char* ... 仅当将此类指针分配给变量并因此会超过字符串本身的生命周期时才会引发编译时错误。


更新:为了进一步简化,看起来问题是由分配引起的:

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

【问题讨论】:

    标签: cython


    【解决方案1】:

    我认为问题出在

    out = out + line
    

    Cython 没有为 C 字符串定义运算符 +。相反,它将它们转换为 Python 字符串并将它们连接起来:

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

    因此,一旦tmp3 被销毁(即刻),out 就会成为无效指针。


    我会避免使用strcat,因为它是not very efficient for repeated uses。而是跟踪当前的字符串长度并自己复制数据。鉴于您的长度未知,您可能希望使用 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)
    

    这是我认为你应该做的粗略概述,并没有真正经过测试。

    【讨论】:

    • 我不确定这是否完全正确。问题似乎不是串联。这是任务。见:pastebin.com/RX2c423Q
    • @BradSolomon 据我了解这个答案,它并不是说问题+,而是说'+'的结果是一个临时的python字节对象和char *out引用其中的数据,由于临时对象超出范围(即刻)而被销毁,从而使 out 成为悬空指针。
    • 我认为来自string.pxd 的东西可能在这里可用:github.com/cython/cython/blob/master/Cython/Includes/libc/…;查看更新的问题
    • 我已经在大纲中编辑了我将如何解决这个问题......不能保证完全正常,但我认为它说明了我将采取的方法
    • @BradSolomon 否 - 如果您在开始时知道字符串长度,那么预先进行分配是有意义的(或者即使您知道上限并且它不是可笑的过大)。事后看来,我现在有点不确定为什么我使用 realloc ...
    猜你喜欢
    • 2015-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-18
    相关资源
    最近更新 更多