对于允许这种转换的类型(例如,如果T1
是POD类型且T2
是unsigned char
),使用static_cast
的方法符合标准。
另一方面,reinterpret_cast
完全由实现定义 - 您所获得的唯一保证是可以将指针类型转换为任何其他指针类型,然后返回原始值;此外,您可以将指针类型强制转换为足以容纳指针值的整数类型(这取决于实现,并且可能根本不存在),然后将其转换回来,您将获得原始值。
更具体地说,我只引用了标准的相关部分,并突出了重要部分:
5.2.10[expr.reinterpret.cast]:
reinterpret_cast
执行的映射是实现定义的。[注意:它可能会产生与原始值不同的表示。]…可以将指向对象的指针显式转换为不同类型的对象的指针。)除了将类型为“指向T1的指针”的rvalue转换为类型“指向T2的指针”(其中T1和T2是对象类型,且T2的对齐要求不比T1严格),然后返回其原始类型的指针值,此类指针转换的结果是未指定的。
因此,像这样的内容:
struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);
“未指定”在 IT 技术中是一个常用术语。
解释为什么 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[basic.types]:
对于任何POD类型T(除了基类子对象),不管这个对象是否持有有效的T类型值,构成该对象的底层字节(1.7)都可以被复制到char或unsigned char数组中。如果将char或unsigned char数组的内容复制回对象中,则对象随后应保持其原始值。
T类型对象的对象表示是由T类型对象占用的N个无符号char 对象序列,其中N等于sizeof(T)。
3.9.2[basic.compound]:
带有cv限定符(3.9.3)或未带cv限定符的类型void*
(指向void的指针)可以用于指向未知类型的对象。一个void*
应当能够容纳任何对象指针。带有cv限定符或未带cv限定符(3.9.3)的void*
应具有与带有cv限定符或未带cv限定符的char*
相同的表示和对齐要求。
3.10[basic.lval]:
如果程序试图通过除以下类型之一以外的lvalue访问对象的存储值,则行为未定义:
- ...
- char或unsigned char类型。
4.10[conv.ptr]:
类型为“指向cv T”的rvalue(其中T是对象类型)可以转换为类型为“指向cv void”的rvalue。将“指向cv T”的指针转换为“指向cv void”的结果指向对象T所在的存储位置的开始,就像对象是类型T的最派生对象(1.8),而不是基类子对象。
5.2.9[expr.static.cast]:
除了lvalue-to-rvalue(4.1),array-topointer(4.2),function-to-pointer(4.3)和boolean(4.12)转换之外的任何标准转换序列(第4章),都可以使用static_cast进行显式转换的逆操作。
[编辑] 另一方面,我们还有这个宝石:
9.2[class.mem]/17:
使用reinterpret_cast转换的POD结构体对象指针,指向其初始成员(如果该成员是位域,则指向其所在单元),反之亦然。[注意:因此,在POD结构体对象内可能存在未命名的填充,但不会在其开头处出现,因为这是为了实现适当的对齐。]
这似乎意味着指针之间的reinterpret_cast转换暗示着“相同的地址”。自己琢磨吧。