C 这种滥用函数声明会调用未定义的行为吗?
考虑以下计划:C 这种滥用函数声明会调用未定义的行为吗?,c,language-lawyer,undefined-behavior,function-declaration,C,Language Lawyer,Undefined Behavior,Function Declaration,考虑以下计划: int main() { int exit(); ((void(*)())exit)(0); } 正如您所看到的,exit是用错误的返回类型声明的,但从未用错误的函数类型调用过。这个程序的行为定义得好吗?我想说,在这两种情况下,它都可能定义得不好 我的论点是,生成的两个调用的代码((void(*)()exit)(0)和退出()可能会有所不同。因此,如果只声明了int exit()(您感兴趣的那一个),主要问题可能是int exit(void)和void exit
int main()
{
int exit();
((void(*)())exit)(0);
}
正如您所看到的,
exit
是用错误的返回类型声明的,但从未用错误的函数类型调用过。这个程序的行为定义得好吗?我想说,在这两种情况下,它都可能定义得不好
我的论点是,生成的两个调用的代码((void(*)()exit)(0)代码>和退出()代码>可能会有所不同。因此,如果只声明了int exit()
(您感兴趣的那一个),主要问题可能是int exit(void)
和void exit(int)
的二进制布局不一定相同
如果还定义了int exit()
,则很可能会由于以下原因导致崩溃。有许多调用约定,例如,当堆栈上保留了返回值的空间时,问题可能会出现。相应地,当((void(*)()退出)(0)时如果使用了code>,那么编译器显然不会在堆栈上保留任何空间(特别是返回值),而函数本身(int exit()
)不知道这一点,因此在运行时仍会尝试将int
返回值推送到预期的内存单元中(本应保留但未保留的部分),这肯定会导致崩溃。我认为这定义了行为。标准的相关部分涉及参数(p6,有点长)和类型:
如果函数定义的类型与
类型(表达式的类型),由表示
调用函数时,行为未定义
所有这些都涉及两个不同的实体,一个是被计算的函数表达式,另一个是被调用的函数。导致表达式上升的标识符(错误声明的退出
)从未进入游戏。因此,在您的情况下,函数被正确调用,并且没有UB
一般来说,如果这是UB,它会破坏很多代码,即在数组中存储函数指针的代码,例如,然后根据一些关于上下文的额外知识通过强制转换调用函数
只是一个吹毛求疵的地方,我认为在这种情况下,你应该帮助编译器并给出一个原型。参数转换形式0
很简单,在这种情况下是正确的,但实际上非常容易出错
((void(*)(int))exit)(0);
那就更好了
更新:鉴于Michael的回答,我同意如果您使用非库函数来完成所有这些,则上述内容是正确的。但7.1.3 p1明确禁止使用与标题中声明的原型不同的标识符exit
,然后第2页声明
如果程序在中的上下文中声明或定义标识符
它是保留的(7.1.4允许的除外),或定义了
保留标识符作为宏名称,行为未定义
MSVC对此程序没有问题,但gcc有问题(至少gcc 4.6.1)。它发出以下警告:
test.c: In function 'main':
test.c:3:9: warning: conflicting types for built-in function 'exit' [enabled by default]
test.c:4:22: warning: function called through a non-compatible type [enabled by default]
test.c:4:22: note: if this code is reached, the program will abort
而且,正如承诺的那样,它在运行时确实会崩溃。崩溃不是错误的调用约定或其他原因造成的意外-gcc实际上生成了一条带有操作码0x0b0f的未定义指令,以显式强制崩溃(gdb将其反汇编为ud2
-我还没有查阅CPU手册中可能会对操作码的说明):
我不愿意说gcc这样做是错误的,因为我相信编写该编译器的人比我更了解C。但以下是我如何阅读标准中关于C的内容;我相信有人会指出我遗漏了什么:
C99谈到函数指针的转换(6.3.2.3/8“指针”):
指向一种类型函数的指针可以转换为指向另一种类型函数的指针,然后再返回;结果应与原始指针进行比较。如果使用转换后的指针调用类型与指向类型不兼容的函数,则行为未定义
在表达式中,标识符exit
计算为函数指针
子表达式((void(*)()exit)
将exit
计算结果的函数指针转换为void(*)(
类型的函数指针。然后通过该指针进行函数调用,传递int
参数0
标准库包含名为exit
的函数,该函数具有以下原型:
void exit(int status);
该标准还规定(7.1.4/2“库函数的使用”):
如果库函数可以在不引用头中定义的任何类型的情况下声明,那么也可以在不包含其关联头的情况下声明和使用该函数
您的程序不包含包含该原型的标头,但通过转换指针进行的函数调用使用了强制转换中提供的“声明”。强制转换中的声明不是原型声明,因此我们需要确定标准库和函数定义的exit
函数类型是否为程序中转换的函数指针的类型是兼容的。标准上说(6.7.5.3/15“函数声明器(包括原型)”)
对于要兼容的两种函数类型,两者都应指定兼容的返回类型……如果一种类型具有参数类型列表,而另一种类型由不属于函数定义一部分且包含空标识符列表的函数声明符指定,则参数列表不应具有省略号终止符和ea类型ch参数应与应用默认参数产生的类型兼容
在我看来,这似乎是一件好事
void exit(int status);