您可以将 "pointer to a function pointer" 转换为 void*

Can you cast a "pointer to a function pointer" to void*

受到对我回答的评论的启发

这一系列步骤在 C 标准 (C11) 中是否合法?

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

或等效于代码:

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.

似乎并未涵盖这一点

您的代码是正确的。

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

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

106) Another way to approach pointer arithmetic is first to convert the pointer(s) to character pointer(s): In this scheme the integer expression added to or subtracted from the converted pointer is first multiplied by the size of the object originally pointed to, and the resulting pointer is converted back to the original type. For pointer subtraction, the result of the difference between the character pointers is similarly divided by the size of the object originally pointed to. When viewed in this way, an implementation need only provide one extra byte (which may overlap another object in the program) just after the end of the object in order to satisfy the ''one past the last element'' requirements.

void* 上的指针运算不在 C 语言中。虽然你没有这样做,但你正在对 char* 进行指针运算,这完全没问题。您可以使用 char* 而不是 void* 作为开头。

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

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

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

  • C 将所有类型分为两大类:对象和函数。指向函数的指针是一个 标量类型 ,它又是一个对象。 (C17 6.2.5)
  • void* 是指向对象类型的指针的通用指针类型。任何指向对象类型的指针都可以隐式转换 to/from 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)。除了关于对齐的问题之外,这样做是明确定义的并且不违反 "strict pointer aliasing",你应该取消引用字符指针(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