使用 reinterpret_cast 将函数转换为 void*,为什么它不违法?

Using reinterpret_cast to cast a function to void*, why isn't it illegal?

本文关键字:为什么 void cast reinterpret 函数 转换 使用      更新时间:2023-10-16

这是我上一个问题的切线跟进 匹配布尔值与常量 void* 重载的函数地址。回答者解释说:

[C++11] 标准未定义来自 "指向函数的指针"到"指向void的指针"。

很难为缺少某些东西提供报价,但是 我能做的最接近的是 C++11 4.10/2 [conv.ptr]:

类型为"指向 cv 的指针 T"的 prvalue,其中 T 是对象类型,可以转换为类型为"指向 cv 的指针"的 prvalue void"。将"指向 cv T 的指针"转换为 "指向 cv void 的指针"指向存储位置的开头 类型为 T 的对象所在的位置,就好像该对象是最 类型 T 的派生对象 (1.8((即,不是基类子对象(。 空指针值将转换为 目标类型。

(强调我的(

假设func被声明为void func();,如果你做一个C式的强制转换,即 (void*) func,演员阵容将成功。 然而,static_cast<void*>(func)是无效的,但reinterpret_cast<void*>(func)会成功。但是,您无法将随后转换的指针转换回其原始类型。例如

好:

int main() {
  int* i;
  void* s = static_cast<void*>(i);
  i = static_cast<int*>(s);
  s = reinterpret_cast<void*>(i);
  i = reinterpret_cast<int*>(s);
}

不行:

void func() { }
int main() {
  void* s = reinterpret_cast<void*>(func);
  reinterpret_cast<decltype(func)>(s);
}

N3337 首先说:

[重新诠释]

表达式 reinterpret_cast<T>(v) 的结果是 将表达式v转换为类型 T 。如果T是左值 引用类型或对函数类型的右值引用,结果为 一个左值;如果 T 是对对象类型的右值引用,则结果为 一个 x值;否则,结果是 prvalue 和 lvalue-to-rvalue (4.1(、数组到指针 (4.2( 和函数到指针 (4.3( 标准 转换是在表达式 v 上执行的。 可以 下面列出了使用reinterpret_cast显式执行。不 其他转换可以使用 reinterpret_cast 显式执行。

我加粗了我认为在这里很关键的语言。最后一部分似乎暗示,如果未列出转换,则这是非法的。简而言之,允许的转换是:

  • 指针可以显式转换为任何足够大的整数类型来容纳它。
  • 整型或枚举类型的值可以显式转换为指针。
  • 函数指针可以显式转换为不同类型的函数指针。
  • 对象指针可以显式转换为不同类型的对象指针。
  • 条件地支持将函数指针转换为对象指针类型,反之亦然。
  • 空指针值 (4.10( 将转换为目标类型的空指针值。
  • 如果 T1T 2 都是函数类型或两个对象类型,则可以显式转换为不同类型的 X prvalue"指向类型 T1 T2 的 Y 成员的指针"。
  • 如果"指向T1的指针"类型的表达式可以使用reinterpret_cast显式转换为"指向T2的指针"类型,则可以将类型T1的左值表达式转换为类型"对T2的引用"。

void*不是函数指针,对象没有函数或 void 类型。

[基本类型]

对象类型是(可能符合 cv 条件的(类型,但不是 函数类型,不是引用类型,也不是 void 类型。

所以也许我抓住了稻草,但这似乎reinterpret_cast<void*>(func)是非法的。但是,另一方面,[expr.static.cast]/5 说:"否则,static_cast应执行下面列出的转换之一。不得进行其他转换使用static_cast明确执行",关键区别是"应"和"可以"。这足以使reinterpret_cast合法还是我错过了其他东西?

(所有引号均来自 N3337,并且对于从那里开始的 N4296 之前的每个草稿都是等效的,即此答案至少对 C++11 和 C++14 有效,但对 C++03 无效,因为此答案的第一个引号不存在。

[expr.reinterpret.cast]/8:

将函数指针转换为对象指针类型,反之亦然 有条件地支持。这种转换的含义是 实现定义,除非实现支持 双向转换,将一种类型的 prvalue 转换为 另一种类型和背部,可能具有不同的简历资格, 应生成原始指针值。

这包含在您的列表中。你认为void不是一个对象类型,但你没有考虑关键的[basic.compound]/3:

指向void的指针类型或指向对象类型的指针的类型称为对象指针类型

(也就是说,对象指针类型不一定是"指向对象类型的指针" - 标准术语使您到达了那里。

唯一的原因

f = reinterpret_cast<decltype(f)>(s);

在 GCC 或 Clang 上不好的是,与源表达式相反,目标类型没有衰减 - 并且您显然不能void*转换为函数类型。您需要使目标类型成为指向函数的指针,然后它就可以工作了。