代码之家  ›  专栏  ›  技术社区  ›  dimba

静态铸造安全

  •  11
  • dimba  · 技术社区  · 16 年前

    afaik,对于指针/引用静态类型转换,如果编译器此时看不到类定义,则 static_cast 会表现得像 reinterpret_cast .

    为什么是 静态铸造 指针/引用不安全,数值安全吗?

    2 回复  |  直到 8 年前
        1
  •  22
  •   Steve Jessop    16 年前

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

    在长:

    #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是一个基类,当然也不知道要应用什么偏移量。

    但是,在没有可见定义的情况下,静态\u cast的行为与重新解释\u 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) 执行您所说的:如果结构C的定义可见,表明B是一个基类,那么它与静态类型转换相同。如果类型只是向前声明的,那么它与重新解释类型转换相同。这是因为它被设计成与C兼容,这意味着它必须在C中可能的情况下做C所做的事情。

    静态类型转换总是知道如何处理内置类型,这就是内置的真正含义。它可以将int转换为float,依此类推。所以这就是为什么它对数值类型总是安全的,但是它不能转换指针,除非(a)它知道指针指向什么,(b)指向类型之间有正确的关系。因此它可以转换 int float ,但不是 int* float* .

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

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

    其中一件事 静态铸造 can do是“downcast”一个指向派生类的指针(在本例中,c是a的派生类)。但是如果引用不是派生类的,那么就注定要失败。一 dynamic_cast 将在运行时执行检查,但对于我的示例类C,不能使用 动态铸造 ,因为a没有虚拟函数。

    你也可以用同样的方法做不安全的事情 静态铸造 往返 void* .

        2
  •  5
  •   AnT stands with Russia    16 年前

    不,你的“afaik”不正确。 static_cast 从不表现得像 reinterpret_cast (除了,也许当你转换成 void * ,尽管通常不认为 重新解释铸模 )

    首先,什么时候 静态铸造 用于指针或引用转换,指定 静态铸造 显式要求类型之间存在某种关系(并且 静态铸造 )对于类类型,它们 应该 通过继承关系,如 静态铸造 . 如果没有完全由点定义的两种类型,则不可能满足该要求。 静态铸造 . 因此,如果定义在 静态铸造 代码根本无法编译。

    举例说明以上内容: 静态铸造 可使用[冗余]执行对象指针向上转换。代码

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

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

    Base *base(derived);
    

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

    也, 静态铸造 可用于执行对象指针向下强制转换。代码

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

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

    Base *base(derived); // reverse direction
    

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

    所以,你根本无法使用 静态铸造 具有未定义的类型。如果编译器允许这样做,那就是编译器中的一个bug。

    静态铸造 由于完全不同的原因,指针/引用可能不安全。 静态铸造 可以对对象指针/引用类型执行分层降级,而不检查对象的实际动态类型。 静态铸造 还可以对方法指针类型执行分层上溯。使用这些未检查的强制转换的结果可能导致未定义的行为,如果不小心完成的话。

    其次,什么时候 静态铸造 与算术类型一起使用,语义完全不同,并且 与上述无关。它只执行算术类型转换。只要它们符合你的意图,它们总是完全安全的(除了范围问题)。实际上,它可能是一种很好的编程风格,可以避免 静态铸造 对于算术转换和使用旧的C样式转换,只是为了在源代码中明确区分始终安全的算术转换和潜在的不安全的层次指针/引用转换。