C语言不同参数数量函数的强制转换及Python/C API相关疑问
解析Python C扩展中的函数强制转换问题
这是个非常棒的问题,触及了Python C扩展底层的一些巧妙(甚至有点“hacky”)的技巧,以及C语言标准里的边缘行为,我来一步步拆解清楚:
疑问1:如何将函数强制转换为参数数量更少的函数类型?多余的参数会如何处理?
首先得明确两个核心点:
- 从C语言标准来说,把接受3个参数的函数指针转换成接受2个参数的函数指针类型,属于未定义行为——标准不保证这种转换后的调用是安全的。
- 但Python的C API故意这么做,而且在所有Python支持的平台上都是安全的,这依赖于平台的函数调用约定(比如常见的cdecl,参数从右往左入栈)。
具体细节:
keywdarg_parrot是一个接受3个PyObject*参数的函数(self,args,keywds),而PyCFunction类型的定义是只接受2个PyObject*参数的函数指针。- 当Python注册这个函数并调用它时,它内部知道这个函数实际需要3个参数,所以会按照3参数的方式把值传递到栈上。
- 虽然函数指针被转换成了
PyCFunction(2参数),但函数本身的实现是3参数的,在符合调用约定的平台上,它能正确从栈上读取到第三个参数——根本不存在“多余参数被处理”的问题,反而是函数指针的类型声明“少报”了参数数量,实际调用时传递的参数是完整的。
Python这么做是为了复用PyCFunction相关的注册逻辑,不用为带关键字参数的函数单独搞一套完全不同的类型体系。
疑问2:为何要先将keywdarg_parrot转换为void(*)(void)类型再转换为PyCFunction?
这本质上是绕过编译器类型检查的小技巧:
- 很多C编译器会直接拒绝“参数数量不同的函数指针之间的直接转换”,比如你直接写
(PyCFunction)keywdarg_parrot,编译器可能会抛出类型不兼容的错误。 - 而
void(*)(void)是一个“通用”的函数指针类型,C语言允许几乎任何函数指针和它之间进行转换(虽然标准里这也是未定义行为的一种,但编译器通常会允许)。 - 先转成
void(*)(void),再转成PyCFunction,就能骗过编译器,让它不报错——Python团队知道这么做在目标平台上是安全的,所以用这个方法来绕过类型检查,实现带关键字参数的函数注册。
简单说就是:直接转编译器不让过,先转成无参数的通用函数指针,再转目标类型,编译器就放行啦。
内容的提问来源于stack exchange,提问作者Godoy
相关产品推荐
相关产品推荐

