【问题标题】:How to explain calling a C function from an array of function pointers?如何解释从函数指针数组调用 C 函数?
【发布时间】:2022-12-05 00:52:50
【问题描述】:

此示例 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))();
}

【问题讨论】:

  • 怎么解释呢?一些迷上 LSD 的白痴决定故意编写难以理解的代码,以证明他是多么“聪明”——但彻底失败了。暗示:真的聪明的代码唤起更像是“Dang that's简单的.非常简单明了!为什么我没有想到这一点?”这样的代码简直是愚蠢的。
  • 这段代码很荒谬,但有问题的行的想法是首先将(数字)地址转换为指向函数的指针,然后取消引用该指针以调用该函数。

标签: c


【解决方案1】:

void *funarray[] = { NULL,NULL,&amp;MyFun,NULL,NULL }; 的行为未由 C 标准定义,因为它将指向函数的指针 (MyFun) 转换为指向对象类型 (void *) 的指针。 C 2018 6.3.2.3 中规定了指针的转换,除了空指针外,它们都没有涵盖对象类型指针和函数类型指针之间的转换。

代码 size_t CallMyFun = (size_t)&amp;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)();

【讨论】:

    【解决方案2】:

    如 cmets 中所述,这比要求的更难理解。让我们逐行来看:

    这将获取数组中元素 2 的地址并将其分配给CallMyFun。请注意,从数组中获取元素的语法很笨拙且不典型。

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

    这将 CallMyFun 转换为指向函数指针的指针 ((int(**)(void))CallMyFun (记住 - 上一行采用了元素的地址,该元素存储函数的地址)。它取消引用它(第一个*)以将其改回指向函数的指针,并调用该函数。

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

    一种更典型的方法是 (full example):

    void *CallMyFun = funarray[2];
    return ((int(*)(void))CallMyFun)();
    

    即使在那种情况下,您也可以采取一些措施来提高可读性,例如使用 typedef 使指向函数类型的指针更具可读性。

    【讨论】:

      【解决方案3】:

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

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

      在上面,我们正在创建一个初始化为最“空”的指针数组(即NULL),但在索引 2 处我们有一个指向MyFunc 的指针。跟踪类型很有帮助,所以在这一点上,我们有 int (*)(void) 类型的 &amp;MyFunc(C 中的函数指针语法有点奇怪,因为 * 和可能的标识符在中间而不是在最后),它立即变成数组中的void *。为什么数组是 void * 类型,而它似乎包含函数指针有点奇怪,但让我们假设有一个很好的理由......

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

      这段代码是一种相当复杂的访问数组和获取第二个元素的方法,方法是将数组的头部转换为size_t,然后添加正确的字节数以获取第二个条目的地址(更简单的方法是@ 987654331@)。在这里,我们再次丢失了类型信息,从最初的 int (*[])(void) 变为 void*[] 再变为 size_t

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

      现在我们知道CallMyFunc 包含一个指向函数指针的指针,即数组中MyFunc 指针的地址,如果我们想调用它,我们需要将它转换回正确的类型并正确地解引用它。因此,展开调用,我们直接有 ((int(**)(void))CallMyFun) 将不正确类型的 CallMyFuncsize_t 转换为双函数指针,即指向函数指针的指针,其语法为 int (**)(void),就像任何其他需要两个*来表示的双指针。接下来,由于 is 还不是函数指针,我们需要取消引用它以获取类型为 int (*)(void) 的指针,因此为 (*((int(**)(void))CallMyFun))。最后,我们要实际调用函数指针so最后一组父项。

      如 cmets 中所述,此代码因更改类型和使用不寻常的访问数组的方式而变得相当混乱;通常最好保持类型一致,因为它可以让编译器帮助您避免错误,并使代码对您自己和其他人更具可读性。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-29
        • 1970-01-01
        • 2019-01-25
        • 2015-03-04
        • 1970-01-01
        相关资源
        最近更新 更多