reinterpret_cast在解引用void指针时的行为

3

this answer的评论区与某人就他提出的建议争论时,我遇到了一些gcc4.8和VS2013无法编译但clang却能够愉快地接受并显示正确结果的代码。

#include <iostream>

int main()
{
    int i{ 5 };
    void* v = &i;
    std::cout << reinterpret_cast<int&>(*v) << std::endl;
}

现场演示。GCC和VC都出现了我预期的错误,抱怨代码试图在reinterpret_cast内解引用void*。因此,我决定在标准中查找这个问题。从N3797开始,§5.2.10/11 [expr.reinterpret.cast]

如果一个类型为T1的glvalue表达式可以通过使用reinterpret_cast将指向T1的指针显式转换为指向T2的指针,那么它就可以被强制转换为“指向T2的引用”类型。结果引用与源glvalue指向相同的对象,但具有指定的类型。对于左值而言,引用转换reinterpret_cast(x)与使用内置的&和*运算符进行转换*reinterpret_cast(&x)具有相同的效果(对于reinterpret_cast(x)也是如此)。不会创建临时对象,不会进行复制,并且不会调用构造函数(12.1)或转换函数(12.3)。
在这种情况下,T1voidT2int,可以使用reinterpret_castvoid*转换为int*。因此,所有要求都得到满足。
根据注释,reinterpret_cast<int&>(*v)的效果与*reinterpret_cast<int*>(&(*v))相同,根据我的计算,这与*reinterpret_cast<int*>(v)相同。
那么这是GCC和VC的错误,还是clang和我误解了这个问题?

1
如果你执行 void* v = &i; *v; 会发生什么?clang 仍然不会抱怨。当将 void* 转换为其他类型时,我应该使用 static_cast 还是 reinterpret_cast 看起来很有趣。(警告:旧的信息可能已过时。) - user1508519
1
根据这里的内容,&*ptr 看起来是合法的,但不等同于 ptr(我猜这就是你在最后一个 reinterpret_cast 中想表达的)。因此,指针仍然需要被解引用。 - chris
@chris 在你链接的问题的评论中,Oli Charlesworth C99 明确规定 &*E 等同于 E。因此,这部分内容是正确的。我想剩下的问题是,如果 Evoid 或者不完整类型会怎样? - Praetorian
@Praetorian,是的,这个注释只适用于C语言,而不是C++。整个话题很混乱,但是即使在C语言中,void *p; &*p;如果是真正的noop,也应该可以正常工作。 - chris
3个回答

3
事实是,类型转换符内的表达式无效。根据§5.3.1 一元运算符的规定:
一元*运算符执行间接引用:应用它的表达式必须是指向对象类型的指针或指向函数类型的指针,并且结果是一个左值,该左值引用到表达式所指向的对象或函数。如果表达式的类型是“指向T的指针”,则结果的类型为“T”。[注意:可以解除对不完整类型(除cv void之外)的指针引用。因此获得的左值只能以有限的方式使用(例如用于初始化引用);此左值必须不会被转换为prvalue,请参见4.1。-结束附注]
void不是对象类型。因此,所有这些都停在那里,整个表达式无效。在这里clang似乎犯了错误。
更重要的是,根据§3.9.1 基本类型的规定,void类型只能在特定的一组情况下使用:
- 只能将void类型的表达式用作表达式语句(6.2),逗号表达式的操作数(5.18),?: 的第二个或第三个操作数(5.16),typeid或decltype的操作数,用于返回类型为void的函数的返回语句中的表达式(6.6.3)或作为显式转换为类型cv void的操作数。
因此,即使解除引用是合法的,您也不能在类型转换中使用void“对象”作为源(除了转换为void类型)。
当这两个表达式都有效时,reinterpret_cast<int&>(*v)可能确实具有与*reinterpret_cast<int*>(&(*v))相同的效果。并不意味着它本身是有效的,只是因为一个表达式可以以有效形式重新编写。

我同意,如果你孤立地看 operator*,这个表达式是没有意义的。我的猜测是 &(*p) 等价于 p,但从 @chris 在评论中提到的问题来看,这似乎并不是情况。 - Praetorian
我收回之前的说法,根据这个评论,C99规范确实说明&*pp是相同的。 - Praetorian
Clang 16在这里改变了行为以匹配gcc/VC。请参见https://releases.llvm.org/16.0.0/tools/clang/docs/ReleaseNotes.html#c-c-language-potentially-breaking-changes。 - TBBle

3

void类型的表达式仅被允许作为return语句中的语法设备,同时你也可以将一个表达式强制转换成void类型,但这就是全部:没有void类型的glvalues,void类型的表达式不指向内存。因此,标准中引用的那段以glvalue开头的话并不适用。所以,clang是错误的。


1
如果vvoid *类型,那么*v就没有意义。问题不在于强制转换,而在于对指向void的指针进行解引用是非法的。

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