您能否将“函数指针指向指针”转换为void *

时间:2018-10-28 16:43:45

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

受到对我的答案here的评论的启发。

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

  1. 制作一个函数指针数组
  2. 获取指向第一个条目的指针,并将该 pointer指针转换为函数指针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似乎没有涵盖这一点。

3 个答案:

答案 0 :(得分:8)

您的代码正确。

指向函数的指针是一个对象,您正在将指向 object 的指针(指向函数指针的指针)强制转换为void指针,然后再次返回;然后最终取消引用指向对象的指针。

对于char指针算法,这由C11的footnote 106引用:

  

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

答案 1 :(得分:4)

是的,代码很好。这里有各种陷阱和转换规则:

  • C将所有类型分为两个主要类别:对象和函数。指向函数的指针是标量类型,而该对象又是对象。 (C17 6.2.5)
  • 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,这是存储在数组中的原始函数指针类型。

如注释中所述,可以通过对函数类型使用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

答案 2 :(得分:0)

void*上的指针算术不是C语言。不过,您没有这样做,而是在char*上进行指针算术,这完全可以。首先,您可以使用char*而不是void*

安德鲁·海恩(Andrew Helne)似乎缺少以下事实:指向函数的指针是对象,并且其类型是对象类型。这是一个简单的事实,并非像其他评论员所暗示的那样笼罩在神秘之中。因此,他反对将指针转换为函数指针的说法是没有根据的,因为指向任何对象类型的指针都可以转换为void*

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