Table of Contents

Python 垃圾回收


Misc

Python 传递数据到 C 的 bug

以下 bug 和 python 的垃圾回收机制有关,看代码

// test.cpp
 
#include <algorithm>
#include <iostream>
#include <cstdint>
 
extern "C" void output_str(uint8_t *buffer) {
    char *s = (char *)buffer;
    std::cout << s << std::endl;
}
# test.py
 
import ctypes
from ctypes import POINTER, c_uint8, c_uint32, c_uint64
 
lib_so = ctypes.cdll.LoadLibrary("test.so")
 
def BP(bytes: bytes):
    return ctypes.cast(bytes, ctypes.POINTER(ctypes.c_uint8))
 
output_str = lib_so.output_str
output_str.argtypes = (POINTER(c_uint8),)
output_str.restype = None
 
def main():
    # 可以保证正常工作 标准写法
    s = "this_is_a_long_long_long_str"
    b = s.encode(encoding="ascii")
    output_str(BP(b))
    # output: this_is_a_long_long_long_str
 
    # 取决于 Python 的垃圾回收机制不一定能复现
    # 一般可以正常工作 但有风险
    output_str(BP("short_str".encode(encoding="ascii")))
    # output: short_str
 
    # 取决于 Python 的垃圾回收机制不一定能复现
    # 一般不能正常工作
    output_str(BP("this_is_a_long_long_long_str_123".encode(encoding="ascii")))
    # output: ???
 
    # 上述第2、第3种写法问题出在哪里呢?
    # 和 Python 的垃圾回收机制有关
    # 程序流程:
    #   声明一个临时字符串,为其分配内存
    #   声明一个 ctypes 的指针,指向这块内存
    #   将指针传递给 C 端,在 C 端访问这块内存
    #   此后,Python 端的临时字符串被释放
    #   此时,虽然 C 端的指针指向此内存,但是 Python 并不知道
    #   Python 认为已经没有变量指向此内存了,则选择时机释放此内存
    #   如果是小块内存,可能稍微延后释放
    #   如果是大块内存,可能就是立即释放
    #   这也就是短字符串可以正常工作的原因
    # 总结:
    #   传递 Python 数据到 C 的时候,特别是以指针的形式
    #   要确保 Python 这边的内存不会被回收
    #   也就是不能用临时变量
 
if __name__ == "__main__":
    main()