从Python 2切换到Python 3使用CFFI时触发Segmentation fault:11报错
报错核心成因
- 触发段错误的核心原因是Python2/3版本与CFFI构建模式的绑定不匹配,你当前使用的是CFFI的嵌入(embedding)构建模式,这种模式生成的动态库会强绑定构建时所用Python解释器的运行时状态:
- 你之前在Python2环境下执行
ffibuilder.compile()生成的p3.dylib,内部链接的是Python2的C运行时库,而实际调用是在Python3解释器进程中执行,Python3进程不存在Python2对应的运行时上下文,@ffi.def_extern()注册的回调函数没有经过对应解释器的初始化流程,函数指针指向无效内存,调用时直接触发段错误(macOS平台下报错标识为Segmentation fault: 11)。
- 你之前在Python2环境下执行
- 现有调用逻辑不符合CFFI嵌入模式的设计规范:普通
dlopen加载通用动态库的写法,只适用于纯C实现、不依赖Python运行时的动态库;而嵌入模式生成的产物本质是把Python解释器嵌入到C侧的程序结构中,不是可被任意Python进程直接dlopen调用的通用库。 - 存在次要隐患:读取头文件时直接过滤所有
#开头的行,可能会误删头文件中依赖的宏定义、前置函数声明,导致编译生成的函数签名和实际调用的签名不匹配。
具体解决步骤
- 全量清理旧编译产物
- 删除所有Python2环境下生成的中间文件,包括
p3.c、p3.o、p3.dylib/p3.so,避免旧的编译产物被误加载。
- 删除所有Python2环境下生成的中间文件,包括
- 统一构建与运行的Python版本环境
- 切换到和调用时完全一致的Python3环境,重新执行
python api.py完成编译,确保生成的动态库内部链接的是Python3版本的libpython3.x.dylib,回调函数注册流程和Python3运行时完全适配。
- 切换到和调用时完全一致的Python3环境,重新执行
- 按实际使用场景调整代码逻辑
- 场景1:如果需求是编写C侧函数,暴露给Python3进程直接调用,不需要做Python逻辑的嵌入,需要把原来的嵌入构建逻辑调整为标准out-of-line模式,替换原来的
embedding_api、embedding_init_code接口,修改后的api.py核心片段如下:import cffi ffibuilder = cffi.FFI() with open('inter.h') as f: data = ''.join([line for line in f if not line.startswith('#')]) ffibuilder.cdef(data) # 替换原embedding_api逻辑,声明C侧函数签名 ffibuilder.set_source("p3", r''' #include "inter.h" ''') # 删除原embedding_init_code的嵌入初始化逻辑,将@ffi.def_extern装饰的fooz逻辑用纯C实现,完成编译后即可直接通过dlopen调用 ffibuilder.compile(verbose=True) - 场景2:如果确实需要用到嵌入模式(即C程序作为主入口调用Python实现的逻辑),不能直接写Python脚本dlopen动态库,需要按照嵌入模式规范编写C主程序,链接
p3.dylib,由C主程序主动触发嵌入的Python解释器初始化流程,初始化完成后再调用fooz函数。
- 场景1:如果需求是编写C侧函数,暴露给Python3进程直接调用,不需要做Python逻辑的嵌入,需要把原来的嵌入构建逻辑调整为标准out-of-line模式,替换原来的
- 修正动态库加载参数
- 保留原调用逻辑的前提下,dlopen加载时需要指定全局符号解析的flag,避免符号解析不全触发内存访问错误,修改后的客户端代码片段如下:
from cffi import FFI import ctypes ffi = FFI() ffi.cdef(""" int fooz(void); """) # 指定加载flag,强制符号立即解析并暴露给全局命名空间 lib = ffi.dlopen("p3.dylib", flags=ctypes.RTLD_NOW | ctypes.RTLD_GLOBAL) print(lib.fooz())
- 保留原调用逻辑的前提下,dlopen加载时需要指定全局符号解析的flag,避免符号解析不全触发内存访问错误,修改后的客户端代码片段如下:
内容的提问来源于stack exchange,提问作者mvm
相关产品推荐
相关产品推荐

