以下 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()