cgo如何编写包装器释放返回给Python的unsafe.Pointer内存
问题根源
- Go侧导出的
FreeCByte参数类型错误,接收的是指针的指针而非实际分配的内存指针,导致释放的是错误的内存地址 - Python侧未正确声明
FreeCByte的参数类型,调用时类型不匹配 - 释放指针后仍然访问指针指向的内存,存在野指针访问风险
修正方案
第一步:修正Go侧释放逻辑
直接接收unsafe.Pointer类型的内存地址,传入C.free即可:
// #include <stdlib.h> import "C" import ( "encoding/binary" "unsafe" ) //export clientpy func clientpy(url *C.char, headersfrompy *C.char, proxy *C.char) unsafe.Pointer { s := C.GoString(url) headers := C.GoString(headersfrompy) p := C.GoString(proxy) request := UrlGet(s, headers, p) length := make([]byte, 8) binary.LittleEndian.PutUint64(length, uint64(len(request))) return C.CBytes(append(length, request...)) } //export FreeCByte func FreeCByte(b unsafe.Pointer) { C.free(b) }
第二步:修正Python侧调用逻辑
- 正确声明
FreeCByte的参数类型 - 将内存访问操作移到释放指针之前
- 修复可变默认参数的隐藏bug
from ctypes import cdll import ctypes, cchardet, json from bs4 import BeautifulSoup lib = cdll.LoadLibrary("./test.so") # 配置clientpy函数类型 lib.clientpy.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p] lib.clientpy.restype = ctypes.c_void_p # 用void_p对应Go的unsafe.Pointer兼容性更好 # 配置FreeCByte函数类型 lib.FreeCByte.argtypes = [ctypes.c_void_p] lib.FreeCByte.restype = None def newrequest(path, lister=None, proxy=None): # 避免可变默认参数导致的状态残留问题 if lister is None: lister = {} if proxy is None: proxy = [] try: print(f"proxy: {proxy}") ptr = lib.clientpy(path.encode("utf-8"), str(lister).encode("utf-8"), str(proxy).encode("utf-8")) # 读取前8位的长度标识 length_ptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte * 8)) length = int.from_bytes(length_ptr.contents, byteorder="little") # 读取完整返回数据 data_ptr = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_ubyte * (8 + length))) data = bytes(data_ptr.contents[8:]) # 所有内存访问操作必须放在释放前执行 print(f'bytes: {data}') # 释放C侧分配的内存 lib.FreeCByte(ptr) return data except Exception as e: print(f"请求出错: {e}") return None
额外说明
你注释掉的free(ptr)也可以正常使用,只要确保调用的free和分配内存的malloc属于同一个C标准库实例即可,使用Go导出的释放函数可以避免跨库内存释放的兼容性问题。
内容的提问来源于stack exchange,提问作者JJ Cauton
相关产品推荐
相关产品推荐

