【问题标题】:Using void (*)() pointers for other functions对其他函数使用 void (*)() 指针
【发布时间】:2020-09-08 01:05:04
【问题描述】:

通过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 参数与函数所接受的参数数量不匹配,这将是未定义的行为,但如果匹配,是否合法?

【问题讨论】:

  • 不是您特定问题的答案,但常见的方法是只使用一个接受 void* 参数的函数指针,然后根据实际实现对其进行不同的解释。然后,此参数可以是从NULL 到指向具有十几个参数的结构的指针的任何内容。如果调用者必须知道参数的数量、类型和顺序,那么整个想法在实践中就变得没那么有用了。
  • 您发布的代码中的间距让我非常生气。
  • 最好从main 中删除演员表。通常,此类转换可能会导致编译器抑制对不正确转换的诊断。
  • 不管这种构造是否严格合法,我认为有更简洁的方法来完成同样的事情(例如,让fun0 接受一个论点并忽略它)。
  • @KonradRudolph:我最初的印象是,在看到代码块的第一秒,仅基于间距,它看起来像汇编语言(基于多个制表符停止的列,就像 asm 会用于mnemonic op1, op2)。我就像“这是来自 HNQ,为什么我在活动的 asm 问题列表中没有看到这个?”我也不喜欢 C 的这种间距!

标签: c function pointers language-lawyer


【解决方案1】:

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

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

对于要兼容的两种函数类型,两者都应指定 兼容的返回类型。此外,参数类型列表,如果 两者都存在,应同意参数的数量和 在使用省略号终止符时;对应参数 应具有兼容的类型。 如果一种类型有参数类型列表 而另一种类型由函数声明器指定,即 不是函数定义的一部分并且包含一个空的 标识符列表,参数列表不应有省略号 终止符和每个参数的类型应兼容 应用默认值产生的类型 参数提升。 如果一种类型具有参数类型列表,并且 其他类型由包含 (可能为空)标识符列表,两者应在数量上一致 参数,每个原型参数的类型应 与应用程序产生的类型兼容 的默认参数提升到的类型 对应的标识符。 (在确定类型 兼容性和复合类型,每个参数声明 with 函数或数组类型被视为具有调整后的类型 并且使用限定类型声明的每个参数都被视为具有 其声明类型的非限定版本。)

所以你有一个typedef 类型:

void()

和具有类型的函数:

void(void)
void(double)

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

如果表示被调用函数的表达式有 不包含原型的类型,整数促销 对每个参数执行,以及具有类型的参数 float 被提升为 double。这些被称为 default 论据提升

第一个函数没有参数,所以它是兼容的。第二个函数有一个 double 参数,它与默认参数提升匹配,因此它也是兼容的。

再举一些例子,以下功能也可以兼容:

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

但这些不会:

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

【讨论】:

    【解决方案2】:

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

    6.11 未来的语言方向

    6.11.6 函数声明符

    1 使用带空括号的函数声明符(不是 原型格式参数类型声明符)已过时 功能。

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

    【讨论】:

    • 谢谢。唉,说这个功能已经过时对我来说似乎有点苛刻。
    • 本节特指函数声明符。就是说将函数声明为void f(); 已过时;这并不是说函数类型void() 或指针类型void (*)() 的存在已经过时。 (我不知道潜在的意图是什么,但文字只讨论了声明符。)
    • @user2357112supportsMonica 这样,上面的代码对于可怜的程序员泛型仍然有效,但 K&R 风格的函数声明 (int main()) 不再有效。许多人在这里省略了void,认为这意味着“没有参数”,尽管它在用作独立声明时表示“未指定的数字”,如果用作部分声明则表示“没有参数但没有原型”一个函数定义。在这两种情况下,如果您不小心传递了参数,编译器都不会发出警告,而在第二种情况下,这绝对是未定义的行为。
    • @user2357 - 声明符是一种用于创建声明的语法结构。有些类型是从声明符派生的。 void (*)() 在指针声明符之上包含一个函数声明符。如果没有创建它的语法,该类型将如何继续存在!??
    • @StoryTeller-UnslanderMonica:void (*)() 不包含函数声明符。声明者声明事物(见 6.7.6)。 void (*)() 没有声明任何东西。 void (*)() 是一个类型名称。
    【解决方案3】:

    正如@StoryTeller 的answer 中提到的,使用带空括号的函数声明符是一个过时的特性,但可以避免:

    #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 以确保合规性。


    回答问题:

    “此外,参数类型列表,如果两者都存在,则参数数量应一致”似乎意味着 funp 和 fun1 的类型不兼容。可以投吗?

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

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

    在代码中,fun1 的指针在对funcall 的调用中被转换为不同的函数指针类型,并在funcall 中转换回原来的类型,因此可以用来调用fun1 .

    【讨论】:

    • 然而,从 dbush 从规范中摘录,“此外,参数类型列表,如果两者都存在,则参数数量应一致”似乎意味着 funp 和 fun1 的类型是不相容。可以投吗?
    • @dmuir 所需的兼容性是在被调用的函数函数调用的表达式之间。在我们到达函数调用表达式之前,是否有一些不同的类型用于传输函数指针并不重要。
    • @M.M 呵呵,有两个条件,第一个是关于类型(这违反了),第二个是关于表达式(很好)。但是,对于我来说,OPs 代码将来应该可以阅读,因为 typedef 不是函数声明,因此是合法的。但是,此答案中的函数声明 main 不是:p
    • @larkey 我不清楚您所说的“第一个”是什么意思,您能否提供标准报价和/或详细说明。此答案中的程序定义明确
    • 我已经修复了main 参数列表(以前从未注意到它!)并添加了有关函数指针转换的内容。
    猜你喜欢
    • 2012-03-15
    • 1970-01-01
    • 2019-03-28
    • 2013-11-14
    • 1970-01-01
    • 2016-03-01
    • 1970-01-01
    • 2016-11-23
    • 2020-06-11
    相关资源
    最近更新 更多