为何双层ctypes.POINTER可适配C语言的char***参数,三层反而报错?
为何双层ctypes.POINTER可适配C语言的char***参数,三层反而报错?
这个问题的核心在于搞清楚C语言的指针层级和ctypes的自动传参规则,咱们一步步拆解来理解:
1. 先明确C函数的参数意图
你的C函数getArrayOfStrings接收char*** paramPtr参数,它的真正作用是:让函数能修改调用者传入的char**变量的值——也就是把函数内部分配的字符串数组(char**类型)的地址,写入到调用者提供的变量里。换句话说,paramPtr是一个「指向char**的指针」,对应ctypes的POINTER(POINTER(POINTER(c_char)))。
2. 双层POINTER能工作的原因
看你Python里的调用代码:
names = ctypes.POINTER(ctypes.POINTER(ctypes.c_char))() my_lib.getArrayOfStrings(names)
这里的names是**POINTER(POINTER(c_char))类型的空指针变量**,对应C语言的char**。
而ctypes有个非常实用的自动转换规则:当函数的argtypes声明为POINTER(T)时,如果你传入一个T类型的实例,ctypes会自动把这个实例的内存地址(相当于C里的&实例)作为参数传递给C函数。
对应到你的场景:
- 生成的绑定里,
getArrayOfStrings.argtypes = [POINTER(POINTER(POINTER(c_char)))],也就是函数期望接收一个「指向POINTER(POINTER(c_char))的指针」(即C的char***); - 你传入的
names正好是POINTER(POINTER(c_char))类型的实例,所以ctypes自动把names的地址(三层指针,char***)传递给函数,完全匹配参数要求; - C函数内部执行
(*paramPtr) = malloc(...)时,paramPtr就是names的地址,*paramPtr直接操作names变量本身,成功把分配的数组地址写入names,后续遍历自然能拿到正确结果。
你也可以用ctypes.byref显式传递地址,效果和自动处理完全一致:
names = ctypes.POINTER(ctypes.POINTER(ctypes.c_char))() my_lib.getArrayOfStrings(ctypes.byref(names))
3. 三层POINTER报错的原因
如果改成用三层POINTER创建变量:
names = ctypes.POINTER(ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))() my_lib.getArrayOfStrings(names)
问题就出在传参的类型不匹配:
- 这个
names是POINTER(POINTER(POINTER(c_char)))类型,对应C的char***; - 你把它传给函数时,ctypes自动取它的地址,也就是传递了一个四层指针(
char****,对应ctypes的POINTER(POINTER(POINTER(POINTER(c_char))))); - 但C函数期望的是
char***,所以它会把这个四层指针当成三层指针来用:函数里的paramPtr现在是char****,执行(*paramPtr)时会取它指向的char***,但你的names初始是NULL,所以*paramPtr就是0x00000000; - 接下来函数尝试给
(*paramPtr)赋值(malloc的结果),也就是往NULL地址写数据,这直接触发了内存访问违规,也就是你看到的错误:OSError: exception: access violation writing 0x0000000000000000
总结
双层POINTER的变量被ctypes自动转换为三层指针,正好匹配C函数char***的参数要求;而三层POINTER的变量被转换为四层指针,和函数期望类型不匹配,导致函数往NULL地址写数据报错。
备注:内容来源于stack exchange,提问作者jpo38
相关产品推荐
相关产品推荐

