如何解释从函数指针数组调用C函数?

kq0g1dla  于 2022-12-11  发布在  其他
关注(0)|答案(2)|浏览(128)

此示例https://godbolt.org/z/EKvvEKa6T使用以下语法调用MyFun()

(*((int(**)(void))CallMyFun))();

有没有C语言对这种混淆语法的分解来解释它是如何工作的?

#include <stdio.h>

int MyFun(void)
{
    printf("Hello, World!");
    return 0;
}

void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };

int main(void)
{
    size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));
    return (*((int(**)(void))CallMyFun))();
}
bvn4nwqk

bvn4nwqk1#

void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };的行为没有被C标准定义,因为它将指向函数的指针(MyFun)转换为指向对象类型的指针(void *)。到指针的转换在C 2018 www.example.com中指定6.3.2.3,并且它们中没有一个涵盖指向对象类型的指针和指向函数类型的指针之间的转换,空指针除外。
代码size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));CallMyFun设置为funarray的元素2的地址,前提是指向整数size_t类型的指针的转换“自然”工作,这不是C标准所要求的,而是有意的。
(*((int(**)(void))CallMyFun))();中,我们有(int(**)(void)) CallMyFun。这意味着将CallMyFun转换为指向一个不带参数的函数的指针,并返回int。当在C中使用异构函数指针表时,需要在类型之间进行转换,因为C没有提供通用函数指针机制,所以我们不能为此指责作者。然而,这不仅转换为指向函数类型的指针,而且转换为指向函数类型的指针,这是错误的。
数组包含指向void的指针,而不是指向函数指针的指针。C标准不要求这些指针具有相同的大小或表示形式。
然后代码应用*,试图检索指针。这是另一个错误。如果指向void的指针具有相同的大小,并且表示具有指向函数指针的指针,访问void *(数组中储存的内容)当做函数的指标(这就是使用此表达式检索它的作用)违反了C 2018 6.5 7中的别名规则。
但是,如果C实现确实支持此功能以及所有先前的转换和问题,则结果是指向函数MyFun的指针,然后应用()调用该函数。
编写代码的正确方法(假设数组必须支持异构函数类型)可以是:

// Use array of pointers to functions (of a forced type).
void (*funarray[])(void) = { NULL, NULL, (void (*)(void)) MyFun, NULL, NULL };

…

// Get array element using ordinary subscript notation.
void (*CallMyFunc)(void) = funarray[2];

// Convert pointer to function’s actual type, then call it.
return ((int (*)(void)) CallMyFunc)();

如果数组可以是同构的,则代码应编写为:

int (*funarray[])(void) = { NULL, NULL, MyFun, NULL, NULL };

…

return funarray[2]();
sdnqo3pr

sdnqo3pr2#

为了理解这里发生了什么,我们应该一段一段地查看代码的每一部分(至少是奇数部分):

void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };

在上面的例子中,我们创建了一个初始化为最“空”的指针数组。(即NULL),但是在索引2处有一个指向MyFunc的指针。我们有int (*)(void)类型的&MyFunc(C中的函数指针语法有点奇怪,因为*和可能的标识符位于中间而不是末尾),这个数组的类型是void *,但它似乎包含函数指针,这有点奇怪,但让我们假设有一个很好的理由。

size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));

这段代码是一种相当复杂的访问数组的方法,通过将数组的头部转换为size_t,然后添加正确的字节数来获得第二个元素的地址(一种更简单的方法是(size_t) &funarray[2])。这里,我们又丢失了类型信息,从最初的int (*[])(void)变成了void*[]再变成了size_t

return (*((int(**)(void))CallMyFun))();

现在我们知道了CallMyFunc包含了一个指向函数指针的指针,也就是MyFunc指针在数组中的地址,如果我们想调用它,我们需要把它转换回正确的类型,并正确地解引用它。我们直接让((int(**)(void))CallMyFun)将类型不正确的CallMyFuncsize_t转换为双精度函数指针,即指向函数指针的指针,其语法为int (**)(void),就像其他的双精度指针一样,需要两个 * 来表示。其次,因为它还不是一个函数指针,我们需要解引用它来得到int (*)(void)类型的指针,所以(*((int(**)(void))CallMyFun))。最后,我们要真正调用函数指针,所以最后一组父指针。
正如注解中提到的,通过更改类型和使用不寻常的数组访问方式,这段代码相当混乱;通常最好保持类型的一致性,因为这样编译器可以帮助您避免错误,并使代码对您自己和其他人更可读。

相关问题