【问题标题】:Invalid ESP when using multiple inheritance in C++ (VS2005)在 C++ (VS2005) 中使用多重继承时 ESP 无效
【发布时间】:2010-01-24 01:11:02
【问题描述】:

我一直在制作一个使用 Box2D 物理引擎的游戏,但我在堆栈指针 (ESP) 和多重继承方面遇到了一些奇怪的问题。我已经设法用最少的代码重现了它,而且似乎我声明要在多重继承中使用的类的顺序似乎决定了程序是否崩溃。

#include <iostream>
#include <string.h>

using namespace std;

class IPhysicsObject
{
public:
    virtual void Collide(IPhysicsObject *other, float angle, int pos)=0;
};

class IBoardFeature
{
public:
    IBoardFeature(){};
    ~IBoardFeature(){};

    virtual bool OnAttach(int x){ return true; }
    virtual bool Update(int x, float dt)=0;
};

/*
class CScorezone : public IBoardFeature, public IPhysicsObject // this breaks !!!
class CScorezone : public IPhysicsObject, public IBoardFeature // this works !!!
*/
class CScorezone : public IBoardFeature, public IPhysicsObject
{
public:
    CScorezone(){}
    ~CScorezone(void){}

    virtual bool Update(int x, float dt)
    {
        return true;
    }

    virtual void Collide(IPhysicsObject *other, float angle, int pos)
    {
    }

    virtual bool OnAttach(int x){ return true; }
};


int main(int argc, char *argv[]) 
{
    CScorezone *scoreZone = new CScorezone();
    CScorezone *otherZone = new CScorezone();

    void *voidZone = scoreZone;
    IPhysicsObject *physZone = static_cast<IPhysicsObject*>(voidZone);
    physZone->Collide(otherZone, 10, 1);

    delete scoreZone;
    delete otherZone;

    // wait for user input
    int x;
    cin >> x;
    return 0;
}

在调试模式下运行会导致以下错误

运行时检查失败 #0 - 值 的 ESP 未正确保存在 函数调用。这通常是一个 调用声明的函数的结果 有一个调用约定 用 a 声明的函数指针 不同的调用约定。

当我进入以下代码行时:

physZone->Collide(otherZone, 10, 1);

我注意到它会进入 CScoreZone::OnAttach,而不是 CScoreZone::Collide。为什么是这样?当我更改 CScoreZone 的继承顺序时,它工作正常

class CScorezone : public IPhysicsObject, public IBoardFeature

我在 Windows XP 上运行 VS2005 SP2 (8.0.50727.768)。有什么想法吗?

【问题讨论】:

  • §5.2.9/10 在标准中对static_cast 说:“指向对象的类型指针的值转换为“指向 cv void 的指针”并返回到原始指针类型将有它原来的价值。”.转换为另一种类型会导致未指定的指针值。

标签: c++ visual-studio-2005 multiple-inheritance stack-pointer


【解决方案1】:

您不必将CScorezone* 分配给void*,然后再将其转换为 IPhysicsObject*。由于CScorezone is-a IPhysicsObject 您可以简单地分配给基指针:

    IPhysicsObject *scoreZone = new CScorezone();
    IPhysicsObject *otherZone = new CScorezone();

IPhysicsObject 声明中还缺少公共虚拟析构函数。

编辑:

我是你在 cmets 中描述的回调情况(通过一些 C api?)我会使用简单的 struct 和指向多态类型的指针来避免未定义的强制转换,例如:

    // one more level of indirection
    struct cb_data
    {
        IPhysicsObject* target;
    };

    // callback function
    int callback( void* data )
    {
        const cb_data& cbd( *static_cast<cb_data*>( data ));

        return cbd.target->Collide( ... );
    }

【讨论】:

  • 我猜 void 强制转换是为了证明一个问题——也许是通过回调传递了一些指针?
  • 是的,void casts 是为了重现它——为了找出碰撞的对象,我填写了一个“用户数据”成员(即 void*)。然后我需要在事情发生冲突时提取该数据。
  • 谢谢你 - 这也可以解决问题。不过,我决定在 void* 之前静态转换为 IPhysicsObject 会更简单一些。
【解决方案2】:

问题是您首先将指针转换为 void*。 编译器不知道如何为指针执行静态转换。 如果使用多重继承来使用第二个超类虚拟表,则需要在强制转换期间更改指针值。 只需在使用 static_cast 之前将指针转换回 CScoreZone*。

【讨论】:

  • 嗯,这解释了问题 - 谢谢。问题是,我现在不确定这将是一个 CScoreZone* - 我只知道它将是一个 IPhysicsObject。有什么方法可以将它推入 void*,将其作为 IPhysicsObject* 取出,并保持 vtable 的完整性?
  • 使用static_cast&lt;void*&gt;(static_cast&lt;IPhysicsObject*&gt;(someZone)) 遵守§5.2.9/10
【解决方案3】:

好吧,在您的代码中,您似乎通过使用void * 作为转换中的中间类型来故意破坏分层转换的完整性。 ScoreZone * 首先转换为 void *,然后转换为 IPhysicsObject *。结果是未定义的行为。

你为什么要这样做?你预计会发生什么?

【讨论】:

  • 我需要 void* 因为这就是 Box2d 存储其物理对象的相关数据的方式。我可能会对其进行更改,以便在其中存储一个包含 IPhysicsObject 的包装类,我可以轻松地在其中进行转换而不会出现这些问题
【解决方案4】:

Nikolai 以您给出的示例告诉您如何避免强制转换。但是,如果您确实需要进行类型转换,则在处理对象时始终使用 dynamic_cast,它会执行运行时类型检查。

【讨论】:

    猜你喜欢
    • 2012-06-02
    • 2017-03-31
    • 2012-05-14
    • 2013-08-28
    • 2011-01-16
    • 1970-01-01
    • 1970-01-01
    • 2015-04-28
    • 1970-01-01
    相关资源
    最近更新 更多