每个 C 标准中带有空括号的函数指针的语义是什么?

What are the semantics of function pointers with empty parentheses in each C standard?

this and this 问题的答案说 return-type (*pointer)() 形式的函数指针是指向接受任意数量参数的函数的指针,尽管后者说它们在 C11 中已过时。

在带有 GCC 的 i386 系统上,调用空括号类型的函数指针时传递的“额外”参数会被忽略,因为堆栈帧的工作方式;例如,

/* test.c */
#include <stdio.h>

int foo(int arg) { return arg; }

int main(void)
{
    int (*fp)() = foo;
    printf("%d\n", fp(267239151, 42, (struct { int x, y, z; }){ 1, 2, 3 }));
    return 0;
}

$ gcc -o test test.c && ./test
267239151
$ 

哪些C标准允许使用空括号的函数指针?无论在哪里,它们的具体含义是什么?

任何函数声明符都可以有空括号(除非它是一个在作用域中已经有 non-void 原型的函数声明)。尽管它是 "obsolescent".

,但并未弃用

在函数指针中,这意味着指针可以指向具有任何参数列表的函数。

注意在实际通过指针调用函数时,参数的类型和数量必须根据函数定义正确,否则行为未定义。

虽然 C 允许您声明一个带有空参数列表的函数(或指向函数的指针),但这并没有改变函数必须用精确的参数定义的事实,每个参数都有精确的类型。 [注1]

如果参数声明在调用点不可见,编译器显然无法对提供的参数执行适当的转换。因此,程序员有责任确保参数的数量正确,并且所有参数的类型都正确。对于某些参数类型,这是不可能的,因为编译器将应用默认参数提升。 [注2]

调用参数数量不正确或参数类型与相应参数类型不兼容的函数是未定义行为。

可见声明具有空参数列表这一事实不会改变调用函数的方式。它只是给程序员增加了更多负担,以确保调用是 well-defined.

指向函数声明的指针同样如此。

简而言之,问题中的示例代码是Undefined Behaviour。它恰好在某些平台上“工作”,但它既不可移植,也不能保证在您重新编译时继续工作。所以唯一可能的建议是“不要那样做”。

如果您想创建一个可以接受额外参数的函数,请使用可变参数声明。 (有关示例,请参见 open。)但请注意限制:被调用的函数必须通过某种方式了解所提供参数的精确数量和类型。


备注

  1. 可变参数函数除外,其原型以 ... 结尾。但是带有空参数列表的声明不能用于调用可变参数函数。

  2. 窄于 int 的整数类型被转换为 intfloat 值被转换为 double.

N1570 6.11.6:

The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.

ISO C 标准的 1990、1999 和 2011 版中出现了相同的措辞。没有任何变化。 obsolescent 一词表示该功能可能会在标准的未来版本中删除,但到目前为止委员会还没有这样做。 (函数指针声明只是可以出现函数声明符的几种上下文之一。)

C 标准的简介部分解释了 obsolescent 的含义:

Certain features are obsolescent, which means that they may be considered for withdrawal in future revisions of this International Standard. They are retained because of their widespread use, but their use in new implementations (for implementation features) or new programs (for language [6.11] or library features [7.31]) is discouraged.

调用使用 old-style 声明符声明的函数仍然需要传递由函数的实际定义定义的正确数量和类型的参数(提升后)。参数不正确的调用具有未定义的行为,这意味着编译器不需要诊断错误;负担完全在程序员身上。

这就是引入原型的原因,以便编译器可以检查参数的正确性。

On an i386 system with GCC, “extra” arguments passed in a call to an empty-parentheses-type’d function pointer are ignored, because of how stack frames work ...

是的,这完全在未定义行为的范围内。未定义行为的最坏症状是让程序完全按照您的预期运行。这意味着您有一个尚未表现出来的错误尚未,并且很难找到它。

你不应该依赖它,除非你有非常很好的理由这样做。

如果你改变

int (*fp)() = foo;

int (*fp)(int) = foo;

编译器会诊断错误的调用。