你可以将“指向函数指针的指针”转换为void *
灵感来自对我的回答的评论。
这一系列步骤在C标准(C11)中是否合法?
- 创建一个函数指针数组
- 获取指向第一个条目的指针并将该指针转换为函数指针
void*
- 对该
void*
执行指针算术void*
- 将其强制转换为指向函数指针的指针并取消引用它。
或者相当于代码:
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指出, 标准部分6.3.2.3似乎没有涵盖这一点:指针 。
是的,代码很好。 这里有各种陷阱和转换规则:
- C将所有类型拆分为两个主要类别:对象和函数。 指向函数的指针是标量类型 ,而标量类型又是一个对象。 (C17 6.2.5)
-
void*
是指向对象类型的指针的通用指针类型。 任何指向对象类型的指针都可以隐式转换为void*
。 (C176.3.2.3§1)。 - 对于函数类型的指针,不存在这样的通用指针类型。 因此,函数指针不能转换为
void*
,反之亦然。 (C176.3.2.3§1) - 但是,任何函数指针类型都可以转换为另一个函数指针类型并返回,允许我们使用类似
void(*)(void)
作为generics函数指针类型。 只要你不通过错误的函数指针类型调用函数,就可以了。 (C176.3.2.3§8)
函数指针指向函数,但它们本身就是对象,就像任何指针一样。 因此,您可以使用void*
指向函数指针的地址 。
因此,使用void*
指向函数指针就可以了。 但不是用它直接指向一个函数。 如果是void *ptr1 = array;
数组衰变成指向第一个元素的指针, void (**)(void)
(在您的示例中等效于voidfunc*
)。 你可以指向一个带有void*
函数指针这样的指针。
此外,关于指针算术:
- 无法对
void*
执行指针运算。 (C17 6.3.2.2)这种算术是应该避免的常见非标准扩展。 而是使用指向字符类型的指针。 - 作为一种特殊情况,指向字符类型的指针可用于迭代任何对象(C176.2.3.3§7)。 除了关于对齐的问题之外,这样做是明确定义的并且不违反“严格指针别名”,如果你取消引用字符指针(C176.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
你的代码是正确的。
指向函数的指针是一个对象,并且您正在向对象 (指向函数指针的指针)转换指针以void
指针void
并再次返回; 然后最终取消引用指向对象的指针。
至于char
指针算术,这由C11的脚注106引用:
106)接近指针算术的另一种方法是首先将指针转换为字符指针:在此方案中,首先将转换后的指针中添加或减去的整数表达式乘以最初指向的对象的大小。 ,并将结果指针转换回原始类型。 对于指针减法,字符指针之间差异的结果类似地除以最初指向的对象的大小。 当以这种方式查看时,实现仅需要在对象结束之后提供一个额外字节(其可以与程序中的另一个对象重叠)以满足“超过最后一个元素”的要求。
void*
上的指针算法不在C语言中。 你不是这样做的,你在char*
上做指针运算,这是完全可以的。 您可以使用char*
而不是void*
开头。
Andrew Helne似乎错过了一个指向函数的指针是一个对象的事实,它的类型是一个对象类型。 这是一个简单明了的事实,不像其他一些评论家似乎暗示的那样掩盖在一片神秘的东西中。 因此,他反对投射指向函数指针的指针是没有根据的,因为指向任何对象类型的指针都可以转换为void*
。
但是,C标准似乎不允许使用(T*)((char*)p + sizeof(T))
代替(p+1)
(其中p
是指向类型数组元素的指针) T
),或者至少我在文中找不到这样的许可。 因此,您的代码可能不合法。