dmu*_*uir 33 c pointers function language-lawyer
通过指针访问指向具有不同参数列表的函数的指针是否合法void (*f)()?下面的程序使用 gcc 编译时没有警告,并且似乎运行正确,但它是合法的 C 吗?
#include    <stdio.h>
#include    <stdlib.h>
typedef void    funp();
static  void    funcall( funp* F, int args, double x)
{
    switch( args)
    {
        case    0:  F();    break;
        case    1:  F(x);   break;
    }
}
static  void    fun0( void)
{
    printf( "zero\n");
}
static  void    fun1( double x)
{
    printf( "one\t%f\n", x);
}
int main( )
{
    funcall( (funp*)fun0, 0, 17.0);
    funcall( (funp*)fun1, 1, 17.0);
    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
我编译了这个
gcc -Wpedantic -Wall -Wextra -std=gnu11 -O2 -o ./funp funp.c
Run Code Online (Sandbox Code Playgroud)
如果nargs参数与函数采用的参数数量不匹配,这将是未定义的行为,但如果匹配则合法吗?
dbu*_*ush 27
在这种特殊情况下,调用是合法的。
C 标准的第 6.7.6.3p15 节阐明了使两个函数类型兼容的原因(相关部分以粗体显示):
对于要兼容的两个函数类型,都应指定兼容的返回类型。此外,参数类型列表(如果两者都存在)应在参数数量和省略号终止符的使用方面达成一致;相应的参数应具有兼容的类型。 如果一种类型具有参数类型列表,而另一种类型由不属于函数定义的一部分且包含空标识符列表的函数声明符指定,则参数列表不应有省略号终止符,并且每个参数的类型应与应用默认参数提升所产生的类型兼容。 如果一种类型具有参数类型列表,而另一种类型由包含(可能为空)标识符列表的函数定义指定,则两者应在参数数量上一致,并且每个原型参数的类型应与类型兼容这是由于将默认参数提升应用于相应标识符的类型而产生的。(在确定类型兼容性和复合类型时,以函数或数组类型声明的每个参数视为具有调整类型,每个以限定类型声明的参数视为具有其声明类型的非限定版本。)
所以你有一个typedef  with 类型:
void()
Run Code Online (Sandbox Code Playgroud)
和函数类型:
void(void)
void(double)
Run Code Online (Sandbox Code Playgroud)
这两个函数定义不使用省略号 ( ...),因此满足第一个条件。对于第二个条件,让我们看看默认参数提升是什么。这些在第 6.5.2.2p6 节中指定:
如果表示被调用函数的表达式的类型不包含原型,则对每个参数执行整数提升,并将具有类型的参数
float提升为double。这些称为默认参数提升。
第一个函数没有参数,所以它是兼容的。第二个函数有一个double参数,它匹配默认参数提升,所以它也是兼容的。
举更多例子,以下函数也将兼容:
void f1(long);
void f2(int);
Run Code Online (Sandbox Code Playgroud)
但这些不会:
void f3(float);
void f4(char);
void f5(short);
Run Code Online (Sandbox Code Playgroud)
        Sto*_*ica 13
正如另一个答案所指出的,您显示的代码今天是有效的 C 。但由于使用了没有参数列表的函数类型,这在未来的任何时候都可能发生变化。
6.11 未来的语言方向
6.11.6 函数声明符
1使用带空括号的函数声明符(不是原型格式参数类型声明符)是一个过时的特性。
过时的功能是指在未来的标准版本中可能会被删除的功能。因此,如果您希望您的代码面向未来,最好避免使用它。
As mentioned in @StoryTeller's answer, the use of function declarators with empty parentheses is an obsolescent feature, but it can be avoided:
#include    <stdio.h>
#include    <stdlib.h>
typedef void    funp(void);
static  void    funcall( funp* F, int args, double x)
{
    switch( args)
    {
        case    0:
            F();
            break;
        case    1:  
            {
                typedef void fn(double);
                ((fn *)F)(x);
            }
            break;
    }
}
static  void    fun0( void)
{
    printf( "zero\n");
}
static  void    fun1( double x)
{
    printf( "one\t%f\n", x);
}
int main( void )
{
    funcall( (funp*)fun0, 0, 17.0);
    funcall( (funp*)fun1, 1, 17.0);
    return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
EDIT: Changed parameter list of main to void for compliance.
In answer to the query:
"Moreover, the parameter type lists, if both are present, shall agree in the number of parameters" would seem to mean that the types of funp and of fun1 are incompatible. Is it ok to cast?
The answer is yes, it is OK to cast. From C11 draft 6.3.2.3 para 8:
指向一种类型函数的指针可以转换为指向另一种类型函数的指针,然后再返回;结果应与原始指针相等。如果使用转换后的指针调用类型与引用类型不兼容的函数,则行为未定义。
在代码中,fun1to的指针在调用中已经转换为不同的函数指针类型funcall,并在调用中转换回原来的类型,funcall所以可以用来调用fun1。