【问题标题】:NULL pointer compatibility with static_castNULL 指针与 static_cast 的兼容性
【发布时间】:2009-12-09 23:09:05
【问题描述】:

第一季度。为什么将 NULL 指针与 static_cast 一起使用会导致崩溃,而 dynamic_cast 和 reinterpret_cast 会返回 NULL 指针?

问题出现在类似于下面给出的方法中:

void A::SetEntity(B* pEntity, int iMyEntityType)
{   
    switch (iMyEntityType)
    {   
    case ENTITY1:
        {
            Set1(static_cast<C*>(pEntity));
            return;
        }
    case ENTITY2:
        {
            Set2(static_cast<D*>(pEntity));
            return;
        }
    case ENTITY3:
        {
            Set3(static_cast<E*>(pEntity));
            return;
        }   
    }
}

Inheritance:
  class X: public B
  class Y: public B
  class Z: public B

  class C: public X, public M
  class D: public Y, public M
  class E: public Z, public M

第二季度。从 B 到 C/D/E 的 static_casting 是否有效? (这工作正常,直到输入变为 NULL)

我使用的是 gcc 版本 3.4.3

【问题讨论】:

  • 你有演示问题的sn-p吗?
  • 发布了一些示例代码。
  • 考虑使用 dynamic_cast 而不是自己使用 switch 语句。
  • 或者最好在基类中使用虚函数。

标签: c++ casting


【解决方案1】:

你可以static_cast一个空指针——它会给你一个空指针。

在您的 sn-p 中,问题很可能是您将 pEntityiMyEntityType 的不一致值传递给函数。因此,当static_cast 完成时,它会盲目地转换为错误的类型(与实际对象不同的类型),并且您会得到一个无效的指针,该指针稍后会向下传递到调用堆栈并导致未定义的行为(使程序崩溃)。 dynamic_cast 在相同的情况下看到对象确实不是预期的类型并返回一个空指针。

【讨论】:

  • 这是可能的,尽管在那种情况下崩溃会更频繁地发生,而不是 NULL 值。我已经对此进行了广泛的测试,并且工作正常。当 pEntity 设置为 null 时发生故障。我已将强制转换更改为“dynamic_cast”并解决了空指针崩溃:(我认为这与在不同强制转换类型中处理空指针的方式有关
  • 你有一个完整的sn-p来演示这个问题吗?类似于 100 行带有 main() 的程序,可以在任何计算机上编译并显示问题。
  • @sharptooth - 尝试了一个简单类的示例,但无法重新创建崩溃。我会继续努力的
【解决方案2】:

你用的是什么编译器?从基类型到派生类型的静态转换可能会导致对指针的调整 - 特别是在涉及多重继承的情况下(根据您的描述,情况似乎并非如此)。不过,没有 MI 还是可以的。

该标准表明,如果正在转换空指针值,则结果将是空指针值(5.2.9/8 静态转换)。但是,我认为在许多编译器上,大多数向下转换(尤其是在涉及单继承时)不会导致指针调整,所以我可以想象编译器可能有一个错误,它不会对 null 进行特殊检查这将需要避免将零值空指针“转换”为一些非零值无意义的指针。我假设要存在这样的错误,您必须做一些不寻常的事情来让编译器不得不调整向下转换中的指针。

看看为您的示例生成了什么样的汇编代码可能会很有趣。

有关编译器如何布局可能需要使用静态强制转换进行指针调整的对象的详细信息,Stan Lippman's "Inside the C++ Object Model" 是一个很好的资源。

Stroustrup's paper on Multiple Inheritance for C++(来自 1989 年)也是一本好书。如果 C++ 编译器有我在这里推测的错误,那就太糟糕了 - Stroustrup 在那篇论文中明确讨论了空指针问题(4.5 零值指针)。

第二个问题:

第二季度。从 B 到 C/D/E 的 static_casting 是否有效?

这是完全有效的,只要当你将 B 指针强制转换为 C/D/E 指针时,B 指针实际上指向 C/D/E 对象的 B 子对象(分别) B 不是虚拟基地。这在标准的同一段落(5.2.9/8 Static cast)中有所提及。我已经突出显示了与您的问题最相关的段落中的句子:

类型“指向 cv1 B”的右值,其中 B 是类类型,可以转换为类型“指向 cv2 D”的右值,其中 D 是从 B 派生的类(第 10 条),如果存在从“指向 D 的指针”到“指向 B 的指针”的有效标准转换 (4.10),cv2 与 cv1 具有相同的 cv 限定或大于 cv1 的 cv 限定,并且 B 不是 D 的虚拟基类。 将空指针值(4.10)转换为目标类型的空指针值。如果“指向 cv1 B 的指针”类型的右值指向实际上是 D 类型对象的子对象的 B,则生成的指针指向 D 类型的封闭对象。 否则,结果演员表未定义。

最后,您可以使用以下方法解决该问题:

Set1(pEntity ? static_cast<C*>(pEntity) : 0);

这是编译器应该为你做的。

【讨论】:

  • 在实际情况下,对象是多重继承的。很抱歉之前没有提到这一点。我已经用继承模型的大致结构更新了这个问题。
【解决方案3】:

static_cast 本身不会导致崩溃 - 它在运行时的行为与reinterpret_cast 相同。您的代码中的其他地方有问题。

【讨论】:

  • 其实这不是真的! reinterpret_cast 可能会移动指针!
  • @PierreBdR - 反过来。 static_cast&lt;&gt; 可以调整指针(派生类的基对象部分可能不在派生类的偏移量 0 处)。
【解决方案4】:

MyClass* p = static_cast&lt;MyClass*&gt;(0) 效果很好。

如果您使用多重继承,那么static_cast 可能会移动您的指针。 考虑以下代码:

struct B1 {};
struct B2 {};

struct A : B2, B1 {
 virtual ~A() {}
};

什么是结构AA 包含一个虚函数表以及B1B2B1 相对于 A 移动。 要将B1 转换为A 编译器需要后移。

如果指向 B1 的指针为 NULL,则 shift 给出无效结果。

【讨论】:

    【解决方案5】:

    static_cast 适用于您知道可以进行强制转换的情况(您可以强制转换为父类,或者您有其他评估类类型的方法)。没有对类型进行运行时检查(因此是 static)。另一方面,dynamic_cast 将在运行时检查对象是否真的属于您想要将其强制转换为的类型。至于reinterpret_cast,除了将相同的内存用于不同的目的之外,它什么也不做。请注意,永远不要使用reinterpret_cast 从一个类更改为另一个类。

    最后,static_cast 在 NULL 指针上崩溃的原因是因为具有继承的 static_cast 可能需要编译器的一些指针运算。这取决于编译器如何实际实现继承。但是在多重继承的情况下,它没有选择。

    看到这一点的一种方式是子类“包含”父类。它的虚拟表包含父表之一,但具有附加功能。如果在开始时添加了特征,那么任何对父类的强制转换都将指向不同的地方……从那里看不到子类的特征。我希望这是有道理的。

    指针运算注意事项

    首先,多继承总是如此,但编译器也可能选择单继承。

    基本上,如果您使用虚拟方法查看对象内容的内存布局,您可以执行以下操作:

    +---------------+----------------+
    | ptr to vtable | members   .... |
    +---------------+----------------+
    

    在单继承的情况下,这已经足够了。特别是,您可以确保任何派生类的 vtable 都以母类的 vtable 开头,并且第一个成员是母类的成员。

    现在,如果你有多重继承,事情就更复杂了。特别是,您可能无法以一致的方式合并 vtable 和成员(至少在一般情况下不是)。所以,假设你从 A、B 和 C 类继承,你可能会有类似的东西:

                           A                       B                      C
    +----------------------+-----------+-----------+----------+-----------+-----+
    | local vtable/members | vtable A  | members A | vtable B | members B | ... |
    +----------------------+-----------+-----------+----------+-----------+-----+
    

    这样,如果您指向 A,您将看到该对象为 A 类型的对象,以及其余部分。但是如果你想看到对象的类型是B,你需要指向地址B,等等。注意,这可能不是系统所做的,但这就是它的git。

    【讨论】:

    • "带有继承的static_cast 可能需要一些指针运算" 你能详细说明一下吗?我认为这可能是可能的原因,因为我正在使用的对象的继承树有点复杂(比给定的示例更复杂)
    猜你喜欢
    • 1970-01-01
    • 2012-01-31
    • 2020-10-11
    • 2018-12-17
    • 2017-12-22
    • 1970-01-01
    • 2021-08-06
    • 2011-12-16
    • 1970-01-01
    相关资源
    最近更新 更多