python 字节对象底层缓冲区重新分配,cffi

python bytes object underlying buffer reallocation, cffi

我正在使用 python 字节对象将一些数据传递给使用 CFFI 库的本机实现的方法,例如:

from cffi import FFI
ffi = FFI()
lib = ffi.dlopen(libname)
ffi.cdef("""
    void foo(char*);
""")
x = b'abc123'
lib.foo(x)

据我了解,本机方法接收到的指针是 x 字节对象后面的实际底层缓冲区的指针。这在 99% 的时间里工作正常,但有时指针似乎失效并且它指向的数据包含垃圾,在本机调用完成后的某个时间 - 本机代码在从初始调用返回后保留指针并期望数据在那里,python 代码确保保留对 x 的引用,以便指针保持有效。

在这些情况下,如果我再次调用具有相同字节对象的本机方法,我可以看到我得到了一个指向相同值但位于不同地址的不同指针,表明bytes 对象已经移动(如果我关于 CFFI 提取指向 bytes 对象包含的底层数组的指针的假设是正确的,并且没有在任何地方创建临时副本),即使据我所知,bytes 对象还没有以任何方式被修改(代码是大型代码库的一部分,但我有理由相信字节对象不会被代码直接修改)。

这里会发生什么?我关于 CFFI 获取指向字节对象实际内部缓冲区的指针的假设是否不正确?是否允许 python 出于垃圾收集/内存压缩的原因静默地重新分配字节对象后面的缓冲区,并且是否没有意识到我持有指向它的指针?我使用的是 pypy 而不是默认的 python 解释器,如果这有所不同的话。

您的猜测是正确答案。 (记录的)保证只是在这种情况下传递的指针在调用期间有效。

PyPy 的垃圾收集器可以移动内存中的对象,如果它们足够小,那么这样做可以提高整体性能。但是,当执行这样的 cffi 调用时,pypy 通常会在调用期间将对象标记为“固定”(除非已经有太多固定对象并且添加更多会严重损害未来的 GC 性能;在这种罕见情况下,它会无论如何都要复制一份,然后再释放它。

如果你的 C 代码需要在调用返回后访问内存,你必须显式地制作一个副本,例如使用 ffi.new("char[]", mybytes),并保持它一直存在根据需要。