调用C函数传入超预期参数是否合法?含函数指针转换场景
背景
我用C语言实现了一个红黑树,为支持可变类型,仅处理const void *类型元素,初始化树时需传入签名为int (*comp)(const void *, const void *)的比较函数。目前一切正常,但我尝试用该C代码构建Python扩展模块,初看很简单,因为Python始终传递对象引用,C例程会将其作为指针接收。
问题
Python对象带有*富比较(rich comparison)*运算符,这意味着在C扩展模块中比较两个任意对象很简单:只需使用int PyObject_RichCompareBool(PyObject *o1, PyObject *o2, int opid)。
但该比较可能返回-1表示对象不可比较。在Python或C++中,抛出异常即可轻松标记异常情况,但C语言没有异常概念,且我无法使用setjmp-longjmp,原因如下:
- 环境缓冲区需被外层函数和内部函数同时知晓
- 执行
longjmp时需释放已分配内存,但内部函数不知道已分配了什么
初步思路
一个简单的解决方案是给比较函数添加第三个参数以标记异常情况,但在纯C环境下使用该库时,这个第三个参数毫无意义。我想起早年学到的知识:C语言中参数以逆序传入栈中,由调用者清理栈,以支持可变参数函数。这意味着只要前两个参数正确,传入第三个参数给预期仅两个参数的函数应该是无害的。
演示代码
#include <stdio.h> // declares a type for the comparison functions typedef int (*func)(); // A simple function for comparing integers - only 2 params int f1(int a, int b) { return a - b; } /* Inserts a value into an increasing array * By convention 0 denotes the end of the array * No size control implemented for brievety * The comp function recieves a pointer to an int * to be able to signal abnormal conditions * */ int insert(int* arr, int val, func comp) { int err = 0; while ((0 != *arr) && (comp(*arr, val, &err) < 0)) { // 1 if (err) return 0; ++arr; } do { int tmp = *arr; *arr = val; val = tmp; } while (0 != *arr++); return 1; } int main() { func f = &f1; // a simple test with 3 parameters int cr = f(3, 1, 5); // 2 printf("%d\n", cr); // demo usage of the insert function int arr[10] = {0}; int data[] = { 1,5,3,2,4 }; for (int i = 0; i < sizeof(data) / sizeof(*data); i++) { insert(arr, data[i], f1); } for (int i = 0; i < sizeof(data) / sizeof(*data); i++) { printf("%d ", arr[i]); } return 0; }
在(1)和(2)处,调用仅需两个参数的函数时传入了三个参数。这段代码在Clang或MSVC中编译甚至不会发出警告,且运行正常,得到预期结果。
核心问题
尽管这段代码在常见实现中运行正常,但我想知道:传入多于预期参数给仅需两个参数的函数是否真的合法,还是会触发未定义行为?
现有调研
- 相关讨论指出,当使用C调用约定时这种操作通常是安全的,但MSVC的
stdcall调用约定不允许这种操作 - 查阅C11标准草案n1570中的6.7.6.3函数声明符(包括原型)和6.5.2.2函数调用部分,但由于语言障碍,无法明确其中是否允许这种操作
说明
本问题的独特之处在于使用了函数指针转换。
内容的提问来源于stack exchange,提问作者Serge Ballesta

