static_cast 安全性

发布于 2024-08-23 10:52:51 字数 170 浏览 12 评论 0原文

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

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

AFAIK, for pointers/references static_cast, if a class definition is not visible to compiler at this point, then static_cast will be behave like reinterpret_cast.

Why is static_cast unsafe for pointers/references and is safe for numeric values?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

岁吢 2024-08-30 10:52:51

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

in long:

#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)所指向的类型之间存在正确的关系。因此它可以将 int 转换为 float,但不能将 int* 转换为 float*

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

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

其中之一 static_cast能做的就是“向下转型”一个指向派生类的指针(在本例中,C 是 A 的派生类)。但如果引用对象实际上不是派生类的,那么你就注定失败了。 dynamic_cast 将在运行时执行检查,但对于我的示例类 C,您不能使用 dynamic_cast,因为 A 没有虚函数。

您也可以使用 static_castvoid* 进行类似的不安全操作。

In short, because of multiple inheritance.

In long:

#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";

}

Output:

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

Note that in order to convert correctly to B*, static_cast has to change the pointer value. If the compiler didn't have the class definition for C, then it wouldn't know that B was a base class, and it certainly wouldn't know what offset to apply.

But in that situation where no definition is visible, static_cast doesn't behave like reinterpret_cast, it's forbidden:

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
}

A plain C-style cast, (B*)(&c) does what you say: if the definition of struct C is visible, showing that B is a base class, then it's the same as a static_cast. If the types are only forward-declared, then it's the same as a reinterpret_cast. This is because it's designed to be compatible with C, meaning that it has to do what C does in cases which are possible in C.

static_cast always knows what to do for built-in types, that's really what built-in means. It can convert int to float, and so on. So that's why it's always safe for numeric types, but it can't convert pointers unless (a) it knows what they point to, and (b) there is the right kind of relationship between the pointed-to types. Hence it can convert int to float, but not int* to float*.

As AndreyT says, there is a way that you can use static_cast unsafely, and the compiler probably won't save you, because the code is legal:

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

One of the things static_cast can do is "downcast" a pointer to a derived class (in this case, C is a derived class of A). But if the referand is not actually of the derived class, you're doomed. A dynamic_cast would perform a check at runtime, but for my example class C you can't use a dynamic_cast, because A has no virtual functions.

You can similarly do unsafe things with static_cast to and from void*.

仙女 2024-08-30 10:52:51

不,你的“据我所知”是不正确的。 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 风格转换可能是一种很好的编程风格,只是为了在源代码中提供始终安全的算术转换和潜在的转换之间的明确区别。 -不安全的分层指针/引用转换。

No, your "AFAIK" is incorrect. static_cast never behaves as reinterpret_cast (except, maybe when you convert to void *, although this conversion is not normally supposed to be carried out by reinterpret_cast).

Firstly, when static_cast is used for pointer or reference conversions, the specification of the static_cast explicitly requires a certain relationship to exist between the types (and be known to static_cast). For class types, they shall be related by inheritance, as perceived by static_cast. It is not possible to satisfy that requirement without having both types completely defined by the point of static_cast. So, if the definition(s) is(are) not visible at the point of static_cast, the code simply will not compile.

To illustrate the above with examples: static_cast can be used [redundantly] to perform object pointer upcasts. The code

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

is only compilable when the following code is compilable

Base *base(derived);

and for this to compile the definition of both types have to be visible.

Also, static_cast can be used to perform object pointer downcasts. The code

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

is only compilable when the following code is compilable

Base *base(derived); // reverse direction

and, again, for this to compile the definition of both types have to be visible.

So, you simply won't be able to use static_cast with undefined types. If your compiler allows that, it is a bug in your compiler.

static_cast can be unsafe for pointers/references for a completely different reason. static_cast can perform hierarchical downcasts for object pointer/reference types without checking the actual dynamic type of the object. static_cast can also perform hierarchical upcasts for method pointer types. Using the results of these unchecked casts can lead to undefined behavior, if done without caution.

Secondly, when static_cast is used with arithmetic types, the semantics is totally different and
has nothing to do with the above. It just performs arithmetic type conversions. They are always perfectly safe (aside form the range issues), as long as they fit your intent. In fact, it might be a good programming style to avoid static_cast for arithmetic conversions and use old C-style casts instead, just to provide a clear differentiation in the source code between always-safe arithmetic casts and potentially-unsafe hierarchical pointer/reference casts.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文