对其他函数使用 void (*)() 指针

Using void (*)() pointers for other functions

通过 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;
}

我用

编译了这个
gcc -Wpedantic -Wall -Wextra -std=gnu11 -O2 -o ./funp funp.c

如果 nargs 参数与函数接受的参数数量不匹配,这将是未定义的行为,但如果匹配是否合法?

在这种特殊情况下,调用是合法的。

C standard 的第 6.7.6.3p15 节阐明了使两个函数类型兼容的原因(相关部分以粗体显示):

For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types. If one type has a parameter type list and the other type is specified by a function declarator that is not part of a function definition and that contains an empty identifier list, the parameter list shall not have an ellipsis terminator and the type of each parameter shall be compatible with the type that results from the application of the default argument promotions. If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier. (In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.)

所以你有一个 typedef 类型:

void()

函数类型:

void(void)
void(double)

两个函数定义都没有使用省略号(...),满足第一个条件。对于第二个条件,让我们看看 默认参数 promotions 是什么。这些在第 6.5.2.2p6 节中指定:

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

第一个函数没有参数,所以兼容。第二个函数只有一个 double 参数,它匹配默认参数 promotions,所以它也是兼容的。

再举一些例子,以下函数也将兼容:

void f1(long);
void f2(int);

但这些不会:

void f3(float);
void f4(char);
void f5(short);

作为另一个答案说明,您显示的代码在 C 今天 有效。但由于使用了不带参数列表的函数类型,这可能会在未来的任何时候发生变化。

6.11 Future language directions

6.11.6 Function declarators

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

过时的功能是指在未来的标准版本中将被删除的功能。因此,如果您希望您的代码经得起未来考验,最好避免使用它。

如@StoryTeller 的 所述,使用带空括号的函数声明符是一个过时的功能,但可以避免:

#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;
}

编辑:将 main 的参数列表更改为 void 以符合要求。


回答查询:

"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?

答案是肯定的,可以投。来自 C11 草案 6.3.2.3 para 8:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

在代码中,指向fun1的指针已在对funcall的调用中转换为不同的函数指针类型,并在funcall内转换回原始类型所以可用于调用 fun1.