C 这是一个通用函数指针吗?它有危险吗?

C 这是一个通用函数指针吗?它有危险吗?,c,pointers,C,Pointers,在学习和弄乱函数指针时,我注意到了一种初始化void函数指针并强制转换它们的方法。然而,尽管我没有收到任何警告或错误,无论是使用GCC还是VS的编译器,但我想知道这样做是危险的还是不好的做法,因为我在Internet上看不到这种经常初始化函数指针的方法。此外,我们是否调用这个通用函数指针 #包括 #包括 #包括 #定义暂停(_getch()) uint16\u t添加(常数uint16\u t x,常数uint16\u t y){ 返回x+y; } 字符颜色(uint8测试){ 返回(char)

在学习和弄乱函数指针时,我注意到了一种初始化void函数指针并强制转换它们的方法。然而,尽管我没有收到任何警告或错误,无论是使用GCC还是VS的编译器,但我想知道这样做是危险的还是不好的做法,因为我在Internet上看不到这种经常初始化函数指针的方法。此外,我们是否调用这个通用函数指针

#包括
#包括
#包括
#定义暂停(_getch())
uint16\u t添加(常数uint16\u t x,常数uint16\u t y){
返回x+y;
}
字符颜色(uint8测试){
返回(char)测试;
}
内部主(空){
无效(*测试)(=(无效*)添加;
常数16_t x=1,y=1;
uint16_t值=((uint16_t(*)()测试)(x,y);
测试=(无效*)chr;
printf(“%d\n”,添加(x,y));//2
printf(“%d\n”,值);//2
printf(“%c\n”,((char(*)()test)(100));//d
暂停;
返回0;
}

这里有两个严重的问题:

  • 从函数指针到对象指针的强制转换(例如
    void*
    )会触发未定义的行为:原则上,它可能会使系统崩溃(尽管在实践中有许多系统可以正常工作)。为此,最好使用函数指针类型,而不是
    void*
  • 您正在欺骗编译器,使其在不知情的情况下将
    int
    传递给需要
    uint8\t
    的函数。这也是未定义的行为,非常危险。由于编译器不知道它正在这样做,它甚至不能采取最基本的必要步骤来避免破坏堆栈-这真是在赌博。类似地,这有点微妙,但您也在欺骗编译器将两个
    int
    -s传递到一个需要两个
    uint16\t
    -s的函数中
  • 还有两个较小的问题:

  • 函数指针类型本身的表示法(例如,在强制转换中)令人困惑。我认为最好使用typedef:
    typedef void(*any_func_ptr)();any_func_ptr foo=(any_func_ptr)(bar)
  • 使用与实际函数不同的签名调用函数指针是未定义的行为。您可以通过仔细的编码来避免这种情况——比您当前的代码更仔细——但这很棘手,也很危险
  • 这是通用函数指针吗

    不,如果我没有大错特错的话,在C中没有“泛型函数指针”这样的东西

    危险吗

    是的。这是邪恶的


    有几件事你需要知道。首先,除非您运行的系统符合POSIX

    void(*test)() = (void*)add;
    
    是错误的。
    void*
    是指向-对象的指针,因此,它与函数指针不兼容。(至少不是在标准C中——正如我提到的,POSIX也要求它与函数指针兼容。)

    第二件事是
    void(*fp)(
    void(*fp)(void)
    是不同的。前一个声明允许
    fp
    获取任何类型的任意数量的参数,并且当编译器看到对函数(指针)的第一次调用时,将推断参数的数量及其类型

    另一个重要方面是,函数指针保证可以相互转换(这体现在它们具有相同的表示和对齐要求)。这意味着,只要不通过指向不兼容类型的指针调用函数,就可以将任何函数指针分配给任何函数(在适当的强制转换之后)。当且仅当在调用指针之前将指针强制转换回原始类型时,该行为才是定义良好的

    因此,如果您想要一个“通用”函数指针,您可以编写如下内容

    typedef void (*fn_ptr)(void);
    
    然后您可以将任何指向函数的指针指定给
    fn\u ptr
    类型的对象。同样,您需要注意的是调用函数时向正确类型的转换,如:

    int add(int a, int b);
    
    fn_ptr fp = (fn_ptr)add; // legal
    fp(); // WRONG!
    int x = ((int (*)(int, int))fp)(1, 2); // good
    

    根据调用约定,特别是谁在执行清理,您可能会使用此方法损坏调用堆栈:对于被调用方清理,编译器无法知道在清理时在堆栈上传递了多少变量,因此,传递错误数量的参数或错误大小的参数将导致调用堆栈损坏

    在x64上,每个人都使用调用者清理,因此在这方面您是安全的。但是,参数值通常会很混乱。在您的示例中,在x64上,它们将是当时相应寄存器中的任何内容。

    C11§6.3.2.3(8)表示:

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

    §6.7.6.3(15)中提到了兼容的功能类型:

    […]如果一种类型具有参数类型列表,而另一种类型由不属于函数定义且包含空标识符列表的函数声明符指定,参数列表不应有省略号终止符,每个参数的类型应与应用默认参数所产生的类型兼容。[……]


    因此,如果您有
    add
    chr
    来获取
    int
    参数(一个int至少有16位的宽度),这是可以的(如果您没有将函数指针强制转换为
    void*
    ),但实际上它是UB。

    “因为我在互联网上看不到这种初始化函数指针的方法。”-你在看哪个互联网?相关:在C中,
    void(*fn)(
    void(*fn)(void)
    有不同的含义。(你不是