static_cast实际上不是对象类型的类型是未定义的行为吗?

Is it undefined behavior to static_cast down a type that isn't actually the type of the object?

本文关键字:类型 未定义 对象 cast 实际上 static      更新时间:2023-10-16

这里是关于四种显式强制转换的堆栈溢出的讨论。 但是我在得票最多的答案中遇到了一个问题。

引用自投票最多的维基答案:

static_cast还可以通过继承层次结构进行强制转换。向上转换(朝向基类)时是不必要的,但是向下转换时,只要不通过virtual继承转换,就可以使用它。但是,它不执行检查,并且将层次结构向下static_cast为实际上不是对象类型的类型是未定义的行为

但是在cppref中,我读到了一些不那么严重的东西:
static_cast < new_type > ( expression )

如果 new_type 是指向某个类 D 的指针或引用,并且表达式类型是指向其非虚拟基 B 的指针或引用,则static_cast执行向下转换。如果 B 是 D 的不明确、不可访问或虚拟基(或虚拟基的基)的,则此向下转换格式不正确。此类static_cast不进行运行时检查以确保对象的运行时类型实际为 D,并且只有在通过其他方式保证此前提条件的情况下才能安全使用

所以在cppref中,它不会说未定义的行为,而是不太严重地表示不安全

因此,当我做这样的事情时:

class A{virtual foo(){}};
class B:public A{};
class C:public B{};
int main()
{
C*pc=new C;
A*pa=static_cast<A*>(pc);//Ok,upcast.
B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**
C* pc1=static_cast<C*>(pb);//downcast back to C;
}

还有一个问题,如果不是UB,是UB取消引用pb吗?

cppreference是用英语编写的,目的是传达良好的理解,它实际上不是规范。但我在这里的措辞没有问题:

此类static_cast不进行运行时检查以确保对象的运行时类型实际上是 D,并且只有在通过其他方式保证此前提条件时才能安全使用。

如果你保证前提条件,那就没问题了。如果你不保证前提条件,那么你的代码是不安全的。代码不安全意味着什么?其行为未定义。

实际规范使用以下措辞:

如果 prvalue 的类型 "指向cv1B的指针" 指向一个实际上是D类型的对象的子对象的B,则生成的指针指向类型D的封闭对象。否则,行为是未定义的。


无论哪种方式,在您的示例中,所有转换都是有效的。

B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**

你错过了条件。向下投掷本身并不是未定义的行为。仅当那里实际上没有派生类型的对象时,它才是未定义的行为。在这种情况下,pa指向一个C,这是一个B,所以投射到一个B是安全的。

然而,这不是:

struct B { };
struct D1 : B { };
struct D2 : B { };
B* p = new D1;
static_cast<D2*>(p); // undefined behavior, no D2 object here

这是定义良好的行为。

类型为">指向 cv1的指针B"的 prvalue,其中B是类类型,可以转换为类型为"指针"的 prvalue 到cv2D",其中D是从B派生的类(第 10 条),如果从 "指针到D" 的有效标准转换 对于"指向B的指针"存在(4.10),CV2CV1相同或更高的 CV 资格,并且B既不是D的虚拟基类,也不是D的虚拟基类的基类。空指针值 (4.10) 转换为目标类型的空指针值。如果 prvalue 的类型 "指向cv1的指针B" 点 对于实际上是类型为D对象的子对象的子对象的B,生成的指针指向封闭对象 类型D.否则,行为是未定义的。

(C++14 [expr.static.cast] (§5.9) ¶11,着重号另加)

pa指向类型为A的对象,该对象实际上是B的子对象,因此第二个强制转换很好,结果指向有效的B。出于同样的原因,你的第三个强制转换是可以的(pb指向CB子对象)。

cpp偏好所表达的"不安全"部分是关于这里没有安全网的事实:你必须通过自己的方式知道尖头物体的实际动态类型是否与你要求的铸件兼容;如果你弄错了,就没有std::bad_castnullptr- 你会得到糟糕的旧的未定义行为。