【问题标题】:Can you cast a "pointer to a function pointer" to void*您可以将“指向函数指针的指针”转换为 void*
【发布时间】:2018-10-28 16:43:45
【问题描述】:

受到 cmets 对我的回答 here 的启发。

此步骤顺序在 C 标准 (C11) 中是否合法?

  1. 创建一个函数指针数组
  2. 获取指向第一个条目的指针并将 指向函数指针的指针转换为 void*
  3. 对该void* 执行指针运算
  4. 将其转换回指向函数指针的指针并取消引用它。

或等效为代码:

void foo(void) { ... }
void bar(void) { ... }

typedef void (*voidfunc)(void);
voidfunc array[] = {foo, bar}; // Step 1

void *ptr1 = array; // Step 2

void *ptr2 = (char*)ptr1 + sizeof(voidfunc); // Step 3

voidfunc bar_ptr = *(voidfunc*)ptr2; // Step 4

我认为这是允许的,因为实际的函数指针只能通过正确类型的指针访问。但 Andrew Henle 指出 Standard section 6.3.2.3: Pointers 似乎没有涵盖这一点。

【问题讨论】:

  • 这种混淆似乎是典型的类型定义指针。甚至到函数。将您的代码与 typedef void voidfunc(void); voidfunc* array[] = ...; 进行对比应该可以准确指出可能存在混淆的地方。
  • @StoryTeller 嗯,很好,我不知道你可以 typedef 函数类型。确实比在 typedef 中隐藏* 更清晰。

标签: c language-lawyer function-pointers c11 pointer-arithmetic


【解决方案1】:

你的代码是正确的。

指向函数的指针是一个对象,您将一个指向 object 的指针(指向函数指针的指针)转换为 void 指针并再次返回;最后解引用一个指向对象的指针。

至于char指针算法,这个是C11的footnote 106引用的:

106) 处理指针运算的另一种方法是首先将指针转换为字符指针:在此方案中,添加到转换后的指针或从转换后的指针中减去的整数表达式首先乘以对象的大小最初指向的指针,并将结果指针转换回原始类型。对于指针减法,字符指针之间的差异的结果类似地除以最初指向的对象的大小。当以这种方式查看时,实现只需要在对象结束后提供一个额外的字节(可能与程序中的另一个对象重叠)以满足“过去最后一个元素”的要求。

【讨论】:

  • 指针算术也是正确的,因为它使用了对char 的强制转换,并且char 不受有效类型规则的约束。
  • @JensGustedt 谢谢,我不知何故跳过了那行:D
【解决方案2】:

是的,代码没问题。这里有各种陷阱和转换规则:

  • C 将所有类型分为两大类:对象和函数。指向函数的指针是一个标量类型,它又是一个对象。 (C17 6.2.5)
  • void* 是指向对象类型的指针的通用指针类型。任何指向对象类型的指针都可以隐式转换为void* 或从void* 转换。 (C17 6.3.2.3 §1)。
  • 不存在用于指向函数类型的指针的此类通用指针类型。因此,函数指针不能转换为void*,反之亦然。 (C17 6.3.2.3 §1)
  • 但是,任何函数指针类型都可以转换为另一种函数指针类型并返回,允许我们使用类似 void(*)(void) 的东西作为通用函数指针类型。只要不通过错误的函数指针类型调用函数就可以了。 (C17 6.3.2.3 §8)

函数指针指向函数,但它们本身就是对象,就像任何指针一样。所以你可以使用void* 来指向函数指针的地址

因此,使用void* 指向函数指针就可以了。但不使用它直接指向一个函数。在void *ptr1 = array; 的情况下,数组衰减为指向第一个元素的指针,即void (**)(void)(在您的示例中相当于voidfunc*)。您可以使用void* 指向这样一个指向函数指针的指针。

另外,关于指针运算:

  • 不能对void* 执行指针运算。 (C17 6.3.2.2)这种算术是一种常见的非标准扩展,应该避免。而是使用指向字符类型的指针。
  • 作为一种特殊情况,指向字符类型的指针可用于迭代任何对象(C17 6.2.3.3 §7)。除了对对齐的关注之外,如果您取消引用字符指针(C17 6.5 §7),这样做是明确定义的并且不会违反“严格指针别名”。

因此,(char*)ptr1 + sizeof(voidfunc); 也可以。然后将void* 转换为voidfunc*,再转换为voidfunc,这是存储在数组中的原始函数指针类型。

如 cmets 中所述,您可以通过对函数类型使用 typedef 来显着提高此代码的可读性:

typedef void (voidfunc)(void);

voidfunc* array[] = {&foo, &bar}; // Step 1
void* ptr1 = array; // Step 2
void* ptr2 = (char*)ptr1 + sizeof(voidfunc*); // Step 3
voidfunc* bar_ptr = *(voidfunc**)ptr2; // Step 4

【讨论】:

    【解决方案3】:

    void* 上的指针运算不在 C 语言中。但是,您并没有这样做,而是在 char* 上进行指针运算,这完全可以。您本来可以使用 char* 而不是 void* 开始。

    Andrew Helne 似乎忽略了一个事实,即指向函数的指针是一个对象,它的类型是一个对象类型。这是一个简单明了的事实,而不是像其他一些评论家所暗示的那样,笼罩在一层神秘的面纱中。所以他反对将指针转换为函数指针是没有根据的,因为指向任何对象类型的指针都可以转换为void*

    但是,C 标准似乎不允许使用(T*)((char*)p + sizeof(T)) 代替(p+1)(其中p 是指向T 类型数组元素的指针),或者至少我不能在文本中找到这样的许可。因此,您的代码可能不合法。

    【讨论】:

    • 您能否扩展您在第三段中表达的保留?不需要明确的“许可”来依赖 C 的语义,而且我认为 C 语义确实定义了 OP(环形交叉口)方法所需的行为,只要不超过 array 的界限。
    • 好吧,(T*)((char*)p + sizeof(T)) 是实现qsort 的唯一方法...
    • @JohnBollinger 我找不到标准在哪里定义(T*)((char*)p + sizeof(T)) 的行为。注意我知道表达式((char*)p + sizeof(T)) 表示数组的第二个元素所在的地址。我看不到标准允许使用以这种方式获得的指针访问该元素的位置。如果您能找到任何内容,请分享相关段落的参考资料。
    • @AnttiHaapala 标准显然不允许有很多常见的做法。它很可能不允许您实现自己的qsort。为什么不?标准的哪一部分允许这样做?
    • @n.m.好吧,首先,您可以迭代对象的字节。真正的问题是,是否允许您使用指向转换为指向 char 的指针的对象的指针迭代 数组的每个字节。如果没有,那么您不能使用自己的memcpy 代替标准库memcpy 来复制数组。
    猜你喜欢
    • 2021-07-22
    • 1970-01-01
    • 1970-01-01
    • 2012-11-21
    • 1970-01-01
    • 1970-01-01
    • 2011-07-31
    • 1970-01-01
    • 2014-10-22
    相关资源
    最近更新 更多