Python ctypes调用带字符串参数的Go共享库函数触发段错误问题求助
问题成因
- 核心触发原因是Go编译为c-shared共享库时,导出函数不能直接使用Go原生
string类型作为参数或返回值。
Go内部的string是固定双字长的内存结构:前8字节是指向字符串实际数据的指针,后8字节是记录字符串长度的整数值,不属于C语言标准支持的ABI兼容类型。
你之前在Python侧给函数配置的c_wchar_p是单字长的指针类型,按照标准C函数调用约定,Go运行时会把你传入的单个指针值,当成Go string结构的第一个字段(数据指针),随后从调用栈的相邻内存区域读取随机的垃圾值,直接当成Go string的长度字段使用。报错里提到的需要分配279362762964992字节块,本质就是读取到的栈上随机垃圾值被识别成了字符串长度,完全没有实际意义,最终因为申请超大内存触发运行时崩溃。 - 次要代码问题:
TestHello里的fmt.Println("%v\n", testArg)属于用法错误,fmt.Println本身不会解析格式化占位符,会直接把原字符串输出,不过这个问题不会触发段错误。
正确的跨语言交互实现
核心原则:所有跨越Go和Python边界传递的字符串,必须统一使用C语言兼容的、以\0作为终止符的char*指针类型传递,不能直接传递Go原生的内存结构。
具体修改分为两侧:
Go 侧修改
将导出函数的字符串参数全部替换为*C.char类型,内部业务逻辑通过C.GoString将C字符串转换为Go原生字符串处理;需要向外返回字符串时,通过C.CString将Go字符串转换为C兼容的char*返回,额外提供专用的内存释放接口,避免C堆上的内存泄漏。
修改后的test.go代码:
package main import ( "C" "unsafe" ) //export TestConcat func TestConcat(testArg *C.char, testArg2 *C.char) *C.char { goArg1 := C.GoString(testArg) goArg2 := C.GoString(testArg2) retval := goArg1 + goArg2 // 分配C堆内存存储返回的字符串 return C.CString(retval) } //export TestHello func TestHello(testArg *C.char) { goArg := C.GoString(testArg) println(goArg) } //export FreeCString // 释放C.CString分配的堆内存,避免泄漏 func FreeCString(s *C.char) { C.free(unsafe.Pointer(s)) } func main() {}
编译命令保持不变:go build -o test.so -buildmode=c-shared test.go
Python 侧修改
- 将之前使用的
c_wchar_p全部替换为ctypes.c_char_p,匹配C侧单字节UTF-8字符串指针的结构 - 显式指定每个导出函数的返回值类型,避免ctypes默认将返回值识别为整数,导致指针读取错位
- 拿到Go侧返回的字符串指针,完成内容读取后,手动调用导出的内存释放接口清理C侧分配的资源
修改后的test.py代码:
import ctypes from ctypes import cdll test_strings = [ "teststring1", "teststring2" ] if __name__ == '__main__': lib = cdll.LoadLibrary("./test.so") # 配置TestConcat的参数类型、返回值类型 lib.TestConcat.argtypes = [ctypes.c_char_p, ctypes.c_char_p] lib.TestConcat.restype = ctypes.c_char_p # 配置TestHello的参数类型 lib.TestHello.argtypes = [ctypes.c_char_p] # 配置内存释放函数的参数类型 lib.FreeCString.argtypes = [ctypes.c_char_p] for test_string in test_strings: # 将Python字符串编码为UTF-8字节流后传入 result_ptr = lib.TestConcat( "hello".encode("utf-8"), test_string.encode("utf-8") ) # 读取指针指向的字符串内容 result = result_ptr.decode("utf-8") print(result) # 手动释放C侧分配的字符串内存 lib.FreeCString(result_ptr) lib.TestHello(test_string.encode("utf-8"))
常见踩坑提醒
- 不要使用
c_wchar_p传递字符串:该类型对应UTF-16编码的宽字符指针,和Go侧默认生成的UTF-8编码C字符串编码不匹配,即便传递过程不报错也会出现乱码。 - 不要直接将Go原生
string作为导出函数的返回值:这类双字长结构没有标准C ABI定义,ctypes默认只能读取单字长返回值,必然出现内存越界。 - 传递字符串时需要手动将Python str编码为UTF-8字节流,不要直接传Python原生str对象给
c_char_p参数,避免不同系统默认编码不一致导致的乱码。
内容的提问来源于stack exchange,提问作者Hugo
相关产品推荐
相关产品推荐

