对于允许进行这种T1
强制转换的类型(例如,如果是 POD 类型并且T2
是unsigned char
),static_cast
标准中明确定义了方法。
另一方面,reinterpret_cast
它完全是实现定义的——你得到的唯一保证是你可以将指针类型转换为任何其他指针类型,然后再返回,你将得到原始值;而且,您可以将指针类型转换为足够大的整数类型以保存指针值(取决于实现,并且根本不需要存在),然后将其转换回来,您将获得原始值。
更具体地说,我将仅引用标准的相关部分,突出显示重要部分:
5.2.10[expr.reinterpret.cast]:
reinterpret_cast 执行的映射是implementation-defined。[注意:它可能会或可能不会产生与原始值不同的表示形式。] ...指向对象的指针可以显式转换为指向不同类型对象的指针。)除了转换类型的右值“指向 T1 的指针”指向类型“指向 T2 的指针”(其中 T1 和 T2 是对象类型,并且 T2 的对齐要求不比 T1 更严格)并返回其原始类型会产生原始指针值,结果这种指针转换是未指定的。
所以是这样的:
struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);
实际上是未指定的。
解释为什么static_cast
有效有点棘手。这是上面重写的代码static_cast
,我相信它可以保证始终按照标准的预期工作:
struct pod_t { int x; };
pod_t pod;
char* p = static_cast<char*>(static_cast<void*>(&pod));
memset(p, 0, sizeof pod);
再次,让我引用标准中的部分,这些部分共同使我得出结论,上述内容应该是可移植的:
3.9 [基本类型]:
对于 POD 类型 T 的任何对象(基类子对象除外),无论该对象是否拥有 T 类型的有效值,构成该对象的底层字节 (1.7) 都可以复制到 char 或 unsigned 数组中字符。如果 char 或 unsigned char 数组的内容被复制回对象,则该对象随后应保持其原始值。
T 类型对象的对象表示是由 T 类型对象占用的 N 个 unsigned char对象的序列,其中 N 等于 sizeof(T)。
3.9.2[基本.复合]:
cv-qualified (3.9.3) 或 cv-unqualified 类型void*
(指向 void 的指针)的对象可用于指向未知类型的对象。Avoid*
应该能够保存任何对象指针。cv-qualified 或 cv-unqualified (3.9.3)void*
应具有与 cv-qualified 或 cv-unqualified 相同的表示和对齐要求char*
。
3.10[基本.lval]:
如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义):
- ...
- 一个 char 或 unsigned char 类型。
4.10 [conv.ptr]:
“指向 cv T 的指针”类型的右值,其中 T 是对象类型,可以转换为“指向 cv void 的指针”类型的右值。将“指向 cv T 的指针”转换为“指向 cv void 的指针”的结果指向类型 T 的对象所在的存储位置的开始,就好像该对象是类型 T 的最派生对象 (1.8) (即,不是基类子对象)。
5.2.9[expr.static.cast]:
除了左值到右值 (4.1)、数组到指针 (4.2)、函数到指针 (4.3) 和布尔 (4.12) 转换之外,可以执行任何标准转换序列的逆转换(第 4 条)显式使用 static_cast。
[编辑]另一方面,我们有这个宝石:
9.2[class.mem]/17:
使用 reinterpret_cast 适当转换的指向 POD 结构对象的指针指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。[注意:因此,在 POD 结构对象中可能存在未命名的填充,但不是在其开头,这是实现适当对齐所必需的。]
这似乎暗示reinterpret_cast
指针之间以某种方式暗示“相同的地址”。去搞清楚。