这种滥用函数声明是否会调用未定义的行为?

R..*_*R.. 6 c undefined-behavior language-lawyer function-declaration

考虑以下程序:

int main()
{
    int exit();
    ((void(*)())exit)(0);
}
Run Code Online (Sandbox Code Playgroud)

如您所见,exit声明的返回类型错误,但从不使用不正确的函数类型调用.这个程序的行为是否定义明确?

Mic*_*urr 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
Run Code Online (Sandbox Code Playgroud)

而且,正如所承诺的那样,它在运行时确实会崩溃。崩溃并不是由于不正确的调用约定或其他原因造成的意外 - gcc 实际上生成了一条未定义的指令,其操作码为 0x0b0f 来显式强制崩溃(gdb 将其反汇编为ud2- 我没有查过 CPU 手册可能会说有关操作码的内容) ):

main:
.LFB0:
    .cfi_startproc
    push    ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    mov ebp, esp
    .cfi_def_cfa_register 5
        .value  0x0b0f
    .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

我不愿意说 gcc 这样做是错误的,因为我确信编写该编译器的人比我更了解 C。但我是这样解读标准的:我确信有人会指出我所缺少的:

C99 关于函数指针的转换是这样说的(6.3.2.3/8“指针”):

指向一种类型函数的指针可以转换为指向另一种类型函数的指针,然后再转换回来;结果应等于原始指针。如果转换后的指针用于调用类型与指向的类型不兼容的函数,则行为未定义。

在表达式中,标识符的exit计算结果为函数指针。

子表达式将求值为((void(*)())exit)的函数指针转换exit为 类型的函数指针void (*)()。然后通过该指针进行函数调用,并传递int参数 0。

标准库包含一个名为的函数exit,其原型如下:

void exit(int status);
Run Code Online (Sandbox Code Playgroud)

该标准还规定(7.1.4/2“库函数的使用”):

如果可以在不引用标头中定义的任何类型的情况下声明库函数,则也可以在不包含其关联标头的情况下声明和使用该函数。

您的程序不包含包含该原型的标头,但通过转换后的指针进行的函数调用使用强制转换中提供的“声明”。强制转换中的声明不是原型声明,因此我们需要确定exit标准库定义的函数类型与程序中转换后的函数指针的函数类型是否兼容。标准说(6.7.5.3/15“函数声明符(包括原型)”)

为了使两个函数类型兼容,两者都应指定兼容的返回类型。...如果一种类型具有参数类型列表,而另一种类型由不属于函数定义的一部分且包含空标识符列表的函数声明符指定,则参数列表不应具有省略号终止符,并且类型每个参数应与应用默认参数提升所产生的类型兼容

在我看来,转换后的函数指针具有兼容的函数类型 - 返回类型相同(void),并且单个参数的类型在int默认参数提升之后。所以在我看来这里没有未定义的行为。


更新:对此进行更多思考后,将 7.1.4/2 解释为必须正确声明“自声明”库函数名称可能是合理的(尽管不一定具有原型,但具有正确的返回值)类型)。特别是因为该标准还规定“以下任何子条款中具有外部链接的所有标识符......始终保留用作具有外部链接的标识符”(7.1.3)。

所以我认为可以合理地论证该程序具有未定义的行为。