指针类型转换的正确方式

69

考虑到以下代码(以及VirtualAlloc()返回一个void*):

BYTE* pbNext = reinterpret_cast<BYTE*>(
    VirtualAlloc(NULL, cbAlloc, MEM_COMMIT, PAGE_READWRITE));

为什么选择使用reinterpret_cast而不是static_cast

我曾认为reinterpret_cast可以用于将指针从整数类型(如DWORD_PTR)转换并转换回来,但是将void*转换为BYTE*,使用static_cast是否可行?

在这种特定情况下,它们有任何(微妙的)差异,还是它们都是有效的指针转换?

C++标准对于此案例有偏好吗?建议使用一种方法而不是另一种方法吗?


12
首先,我不会假设微软的开发人员遵循“事实上的良好实践”。在这里使用 static_cast 是完全合适的。 - user529758
3
应该优先使用static_cast,但有些人更喜欢使用reinterpret_cast,因为名称表明了你正在做什么(重新解释位模式)。 - Jesse Good
2
无论如何,+1,享受你的“好问题”徽章! - user529758
3个回答

61

对于将可转换指向基础类型的指针,这两个强制类型转换具有相同的含义; 因此你正确地使用了static_cast

当在某些指针类型之间进行转换时,可能需要改变指针中保存的特定内存地址

这就是两种强制类型转换的区别所在。 static_cast会进行适当的调整,而reinterpret_cast则不会。

因此,一个好的通用规则是,在指针类型之间使用static_cast,除非你知道需要使用reinterpret_cast


32
您应该使用static_cast。在撤销隐式转换的情况下使用static_cast

然而,在这种特殊情况下,没有区别,因为您正在从void*转换。但是一般来说,在两个对象指针之间进行reinterpret_cast被定义为(§5.2.10 / 7):

对象指针可以显式地转换为不同类型的对象指针。当类型为“指向T1的指针”的prvalue v 转换为类型“指向cv T2的指针”时,如果T1T2都是标准布局类型,并且T2的对齐要求不比T1更严格,或者任一类型是void,则结果为static_cast<cv T2*>(static_cast<cv void*>(v))。将类型为“指向T1的指针”的prvalue转换为“指向T2的指针”(其中T1T2是对象类型,T2的对齐要求不比T1更严格),然后返回到其原始类型会产生原始指针值。任何其他这类指针转换的结果都是未指定的。

强调我的。由于您的T1已经是void*,因此在reinterpret_cast中将其转换为void*不起作用。这在一般情况下是不正确的,这就是Drew Dormann说的内容

#include <iostream>

template <typename T>
void print_pointer(const volatile T* ptr)
{
    // this is needed by oversight in the standard
    std::cout << static_cast<void*>(const_cast<T*>(ptr)) << std::endl;
}

struct base_a {};
struct base_b {};
struct derived : base_a, base_b {};

int main()
{
    derived d;

    base_b* b = &d; // implicit cast

    // undo implicit cast with static_cast
    derived* x = static_cast<derived*>(b);

    // reinterpret the value with reinterpret_cast
    derived* y = reinterpret_cast<derived*>(b);

    print_pointer(&d);
    print_pointer(x);
    print_pointer(y);
}

输出:

00CBFD5B
00CBFD5B
00CBFD5C

(注意,因为y实际上不指向derived,所以使用它是未定义的行为。)

在这里,reinterpret_cast得到了一个不同的值,因为它经过了void*。这就是为什么你应该在可以使用static_cast时使用它,在必须使用reinterpret_cast时使用它的原因。


不错的说明!关于 void* 转换 - 我有这样一个令人烦恼的记忆,具体地说,标准允许 reinterpret_cast 改变值,仅在循环转换 X*void* 再到 X* 时保证地址不变。这个理解是正确的吗? - Drew Dormann
@DrewDormann:我不确定我理解了,您指的是哪一行?对于reinterpret_cast,所有带有void*(无论是源类型还是目标类型)的情况都会经过我引用的子句,因此从这一点上来看,唯一相关的事情就是关于void*static_cast。希望这样能澄清问题。 - GManNickG
@GManNickG,您对这个问题有什么看法吗?根据这里的答案,我认为在这里使用两个静态变量更安全,但如果我错了,请您纠正我。 - Ali
@GManNickG 你能解释一下 print_pointer 函数中的 "..needed by oversight in the standard" 部分吗?为什么不直接用 cout 打印 ptr - User 10482
@User10482 我早就忘记了,但如果我猜的话,可能是因为缺少了对volatile限定的过载。当然,在这个例子中完全没有必要包含这个。 - GManNickG

8
使用static_cast将指针转换为和从void*转换保证地址保持不变。
另一方面,reinterpret_cast保证如果您将指针从一种类型转换为另一种类型,然后再转换回原始类型,则地址将保持不变。
虽然在大多数实现中,使用这两种方法会得到相同的结果,但应优先选择static_cast
而且,在C++11中,我记得使用reinterpret_cast用于void*具有明确定义的行为。在此之前,此行为是被禁止的。
It is not permitted to use reinterpret_cast to convert between pointers to object type and pointers to void.

提议的解决方案(2010年8月): 将5.2.10 [expr.reinterpret.cast]第7段改为以下内容: 如果T1和T2都是标准布局类型(3.9 [basic.types]),并且T2的对齐要求不比T1更严格,或者任一类型为void,则对象指针可以显式转换为不同类型的对象指针。当类型为“指向T1的指针”的prvalue v转换为类型“指向cv T2的指针”时,结果为static_cast(static_cast(v))。 将类型为“指向T1的指针”的prvalue转换为类型为“指向T2的指针”的prvalue(其中T1和T2是对象类型,且T2的对齐要求不比T1更严格),然后再转回其原始类型,就会得到原始指针值。任何其他这样的指针转换的结果是未指定的。 更多信息请参见此处

感谢Jesse Good提供的链接。


2
这里有一个关于reinterpret_cast的链接,在C++11之前它实际上是被禁止的。 - Jesse Good

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接