这个问题是关于使用不完全兼容的函数指针,但我希望只要我的代码只依赖于兼容的部分,我就可以使用它。让我们从一些代码开始:
typedef void (*funcp)(int* val);
static void myFuncA(int* val) {
*val *= 2;
return;
}
static int myFuncB(int* val) {
*val *= 2;
return *val;
}
int main(void) {
funcp f = NULL;
int v = 2;
f = myFuncA;
f(&v);
// now v is 4
// explicit cast so the compiler will not complain
f = (funcp)myFuncB;
f(&v);
// now v is 8
return 0;
}
虽然myFuncA
和myFuncB
的参数相同且完全兼容,但返回值不是,因此只是被调用代码忽略。我tried the above code,它使用GCC正常工作。
我从here和here到目前为止所学到的是,这些函数与标准的定义不兼容,可能导致未定义的行为。然而,我的直觉告诉我,我的代码示例仍然可以正常工作,因为它不依赖于不兼容的部分(返回值)。但是,在this question的答案中,已经提到了堆栈的可能损坏。
所以我的问题是:我的示例是否高于有效的C代码,因此始终按预期工作(没有任何副作用),还是依赖于编译器?
编辑:
我想这样做是为了使用“功能更强大”的“更强大”的功能。在我的示例中,funcp
是界面,但我想提供其他功能,例如myFuncB
以供选择使用。
答案 0 :(得分:4)
同意这是不明确的行为,不要这样做!
是代码函数,即它没有倒下,但返回void后分配的值是未定义的。
在一个非常古老的版本中," C"返回类型未指定,int和void函数可以安全地'混合。在指定的累加器寄存器中返回的整数值。我记得使用这个功能编写代码'!
对于您可能返回的几乎任何其他内容,结果可能是致命的。
前进几年,浮点返回值通常使用fp协处理器(我们仍然在80s)寄存器返回,因此你不能混合int和float返回类型,因为如果调用者没有剥离该值,或者剥离一个从未放在那里的值并导致fp异常,则协处理器会混淆。更糟糕的是,如果使用fp仿真构建,则可以在堆栈上返回fp值,如下所述。此外,在32位构建中,可以传递64位对象(在16位构建中,您可以拥有32位对象),这些对象将使用多个寄存器或堆栈返回。如果它们在堆栈中并分配了错误的大小,那么将发生一些局部踩踏,
现在,c支持struct返回类型和返回值复制优化。如果您没有正确匹配类型,所有投注均已关闭。
此外,某些函数模型会让调用者为调用的参数分配堆栈空间,但函数本身会释放堆栈。调用者和实现对参数的数量或类型以及返回值之间的分歧将是致命的。
默认情况下,C函数名称被导出并链接未修饰 - 只是函数名称定义了符号,因此程序的不同模块可能有不同的函数签名视图,这些视图在链接时会发生冲突,并可能产生非常有趣的运行时错误。
在c ++中,函数名称高度装饰主要是为了允许重载,但它也有助于避免签名不匹配。这有助于保持参数的步骤,但实际上,(如@Jens所述)返回类型未编码到装饰名称中,主要是因为返回类型未被使用(不是,但我认为偶尔可以影响重载决议。
答案 1 :(得分:3)
是的,这是一种未定义的行为,如果想要编写可移植代码,就不应该依赖未定义的行为。
具有不同返回值的函数可以具有不同的调用约定。您的示例可能适用于小型返回类型,但是当返回大型结构(例如,大于32位)时,某些编译器将生成在临时内存区域中返回struct的代码,该区域应该被调用者清理。