14

AFAIK,对于指针/引用 static_cast,如果此时编译器看不到类定义,则static_cast其行为类似于reinterpret_cast.

为什么static_cast指针/引用不安全而数值安全?

4

2 回答 2

24

简而言之,因为多重继承。

长篇:

#include <iostream>

struct A { int a; };
struct B { int b; };
struct C : A, B { int c; };

int main() {
    C c;
    std::cout << "C is at : " << (void*)(&c) << "\n";
    std::cout << "B is at : " << (void*)static_cast<B*>(&c) << "\n";
    std::cout << "A is at : " << (void*)static_cast<A*>(&c) << "\n";

}

输出:

C is at : 0x22ccd0
B is at : 0x22ccd4
A is at : 0x22ccd0

请注意,为了正确转换为 B*,static_cast 必须更改指针值。如果编译器没有 C 的类定义,那么它就不会知道 B 是一个基类,当然也不会知道要应用什么偏移量。

但是在没有可见定义的情况下,static_cast 的行为不像 reinterpret_cast,这是被禁止的:

struct D;
struct E;

int main() {
    E *p1 = 0;
    D *p2 = static_cast<D*>(p1); // doesn't compile
    D *p3 = reinterpret_cast<D*>(p1); // compiles, but isn't very useful
}

一个普通的 C 风格转换,(B*)(&c)如你所说:如果 struct C 的定义是可见的,表明 B 是一个基类,那么它与 static_cast 相同。如果类型只是前向声明,那么它与 reinterpret_cast 相同。这是因为它被设计为与 C 兼容,这意味着它必须在 C 中可能的情况下执行 C 所做的事情。

static_cast 总是知道对内置类型做什么,这就是内置的真正含义。它可以将 int 转换为 float,等等。所以这就是为什么它对于数字类型总是安全的,但是它不能转换指针,除非(a)它知道它们指向什么,并且(b)在所指向的类型之间存在正确的关系。因此它可以转换intfloat,但不能转换int*float*

正如 AndreyT 所说,有一种方法可以static_cast不安全地使用,编译器可能不会拯救你,因为代码是合法的:

A a;
C *cp = static_cast<C*>(&a); // compiles, undefined behaviour

可以做的一件事static_cast是“向下转换”指向派生类的指针(在这种情况下,C 是 A 的派生类)。但是,如果引用实际上不是派生类的,那你就完蛋了。Adynamic_cast会在运行时执行检查,但对于我的示例类 C 你不能使用 a dynamic_cast,因为 A 没有虚函数。

您可以类似地使用static_castto 和 from做不安全的事情void*

于 2010-03-04T19:31:06.323 回答
6

不,您的“AFAIK”不正确。static_cast从不表现得像reinterpret_cast(除了,也许当你转换为 时void *,虽然这种转换通常不应该由 执行reinterpret_cast)。

首先,当static_cast用于指针或引用转换时,static_cast明确的规范要求类型之间存在一定的关系(并且是已知的static_cast)。对于类类型,它们通过继承相关,如static_cast. 如果不完全由点定义这两种类型,就不可能满足该要求static_cast。因此,如果定义在 处不可见static_cast,则代码根本无法编译。

用示例来说明上述内容:static_cast可以[冗余地]用于执行对象指针向上转换。编码

Derived *derived = /* whatever */;
Base *base = static_cast<Base *>(derived);

仅当以下代码可编译时才可编译

Base *base(derived);

为了编译这两种类型的定义,必须是可见的。

此外,static_cast可用于执行对象指针向下转换。编码

Base *base = /* whatever */;
Derived *derived = static_cast<Derived *>(base);

仅当以下代码可编译时才可编译

Base *base(derived); // reverse direction

同样,为了编译这两种类型的定义,必须是可见的。

因此,您根本无法使用static_cast未定义的类型。如果您的编译器允许这样做,那么这是您的编译器中的一个错误。

static_cast由于完全不同的原因,指针/引用可能不安全。static_cast可以对对象指针/引用类型执行分层向下转换,而无需检查对象的实际动态类型。static_cast还可以对方法指针类型执行分层向上转型。如果不小心使用这些未经检查的强制转换的结果,可能会导致未定义的行为。

其次,static_cast与算术类型一起使用时,语义完全不同,与上述无关。它只执行算术类型转换。只要它们符合您的意图,它们总是完全安全的(除了范围问题)。事实上,避免static_cast算术转换并使用旧的 C 风格转换可能是一种很好的编程风格,只是为了在源代码中提供始终安全的算术转换和潜在不安全的分层指针/引用转换之间的明显区别。

于 2010-03-04T19:48:02.647 回答