【问题标题】:Exception thrown: read access violation. it was 0xFFFFFFFFFFFFFFFF抛出异常:读取访问冲突。它是 0xFFFFFFFFFFFFFFFF
【发布时间】:2019-12-23 16:11:07
【问题描述】:

我正在尝试为 Windows 控制台编写 Space Invaders 克隆,感觉一切正常,但这次崩溃出现了。该程序编译没有错误,但在启动时立即崩溃。 我承认我不是很了解指针,我相信问题出在哪里。

#include "stdafx.h"
#include <vector>
#include <random>
#include <chrono>
#include <thread>
#include <memory>
#include <SDKDDKVer.h>
#include "Vector2D.h"
#include "Renderer.h"

std::default_random_engine rGen;
typedef std::uniform_int_distribution<int> intRand;
typedef std::uniform_real_distribution<float> floatRand;

char ObjectType[][64] =
{
    "ot_AlienShip",
    "ot_PlayerShip",
    "ot_AlienLaser",
    "ot_PlayerLaser",
    "ot_Explosion"
};

class PlayField;
class GameObject
{
public:
    char* m_objType = nullptr;
    Vector2D pos;
    unsigned char sprite;

    virtual void Update(PlayField& world) {};
    virtual bool DecreaseHealth() { return true; };
};

class Input
{
public:
    virtual bool Left() = 0;
    virtual bool Right() = 0;
    virtual bool Fire() = 0;
};

class RndInput : public Input
{
public: 
    virtual bool Left() override { floatRand keyRate(0, 1); return (keyRate(rGen) < 0.3f); }
    virtual bool Right() override { floatRand keyRate(0, 1); return (keyRate(rGen) < 0.4f); };
    virtual bool Fire() override { floatRand keyRate(0, 1); return (keyRate(rGen) < 0.5f); };
};

class PlayField
{
private:
    typedef GameObject* GameObjPtr;
    std::vector<GameObjPtr> gameObjects;


public:
    Input* cotrollerInput = nullptr;
    GameObject* it = new GameObject;
    //it = new GameObject;
    Vector2D bounds;
    // Number of available active laser slots for aliens and player
    int AlienLasers = 10;
    int PlayerLasers = 4;

    explicit PlayField(Vector2D iBounds) : bounds(iBounds) {};
    const std::vector<GameObjPtr>& GameObjects() { return gameObjects; }

    void Update()
    {
        // update list of active objects in the world
        for (auto it : gameObjects)
        {
            it->Update(*this);  //The crash is here "Unhandled exception thrown: read access violation. it was 0xFFFFFFFFFFFFFFFF."
        }
    }

    GameObject* GetPlayerObject()
    {
        auto it = std::find_if(gameObjects.begin(), gameObjects.end(), [](GameObjPtr in) { return (strcmp(in->m_objType,"ot_PlayerShip")==0); });
        if (it != gameObjects.end())
            return (*it);
        else
            return nullptr;
    }

    void SpawnLaser(GameObject* newObj)
    {
        if (strcmp(newObj->m_objType, "ot_AlienLaser")==0)
            AlienLasers--;
        else if (strcmp(newObj->m_objType, "ot_PlayerLaser")==0)
            PlayerLasers--;
        AddObject(newObj);
    }
    void DespawnLaser(GameObject* newObj)
    {
        if (strcmp(newObj->m_objType, "ot_AlienLaser")==0)
            AlienLasers++;
        else if (strcmp(newObj->m_objType, "ot_PlayerLaser")==0)
            PlayerLasers++;
        RemoveObject(newObj);
    }

    void AddObject(GameObject* newObj)
    {
        //gameObjectsToAdd.push_back(GameObjPtr(newObj));
        gameObjects.push_back(newObj);
    }
    void RemoveObject(GameObject* newObj)
    {
        //gameObjectsToRemove.push_back(newObj);
        auto it = std::find_if(gameObjects.begin(), gameObjects.end(), [&](GameObjPtr in) { return (in==newObj); });
        gameObjects.erase(it);
    }
};

class Explosion : public GameObject
{
public:
    // Explosion lasts 5 ticks before it dissappears
    int timer = 5;
    Explosion() { m_objType = new char[64];  strcpy(m_objType, "ot_Explosion"); sprite = RS_Explosion; }
    ~Explosion() { delete[] m_objType; }
    void Update(PlayField& world) override
    {
        timer--;
        if (!timer)
        {
            world.RemoveObject(this);
            delete this;
        }
    }
};

class AlienLaser : public GameObject
{
public:
    AlienLaser() { m_objType = new char[64]; strcpy(m_objType, "ot_AlienLaser"); sprite = RS_AlienLaser; }
    ~AlienLaser() { delete[] m_objType; }

    void Update(PlayField& world) override
    {
        bool deleted = false;
        pos.y += 1.f;
        if (pos.y > world.bounds.y)
        {
            deleted = true;
        }
        GameObject* player = world.GetPlayerObject();
        if (player && pos.IntCmp(player->pos))
        {
            deleted = true;
            //Spawn explosion, kill player
            GameObject& no = *(new Explosion);
            no.pos = pos;
            world.AddObject(&no);
            world.RemoveObject(player);
        }

        if (deleted)
        {
            world.DespawnLaser((GameObject*)this);
            delete this;
        }
    }
};

class PlayerLaser : public GameObject
{
public:
    PlayerLaser() { m_objType = new char[64]; strcpy(m_objType, "ot_PlayerLaser"); sprite = RS_PlayerLaser; }
    ~PlayerLaser() { delete[] m_objType; }

    void Update(PlayField& world) override
    {
        bool deleted = false;
        pos.y -= 1.f;
        if (pos.y < 0)
        {
            deleted = true;
        }

        for (auto it : world.GameObjects())
        {
            if (strcmp(it->m_objType,"ot_AlienShip")==0 && it->pos.IntCmp(pos))
            {
                deleted = true;
                //Spawn explosion, kill the alien that we hit
                GameObject& no = *(new Explosion);
                no.pos = pos;
                world.AddObject(&no);
                if (it->DecreaseHealth())
                    world.RemoveObject(it);
            }
        }

        if (deleted)
        {
            world.DespawnLaser(this);
            delete this;
        }
    }
};

class Alien : public GameObject
{
public:
    Alien() { m_objType = new char[64]; strcpy(m_objType, "ot_AlienShip"); sprite = RS_Alien; }
    ~Alien() { delete m_objType; }

private:
    const float maxUpdateRate = 0.01f;
    const float transformEnergy = 1.f;
    enum AlienState
    {
        as_Normal,
        as_Better
    };
    // Variables dictating energy level for upgrade, direction of movement, and current speed
    float health = 1.f;
    float energy = 0.f;
    float direction = 1.f;
    float velocity = 0.5f;
    AlienState state{};

    void Transform()
    {
        state = as_Better;
        sprite = RS_BetterAlien;
        velocity *= 2.f;
    }
    bool DecreaseHealth() override { health -= 1.f; return health <= 0;  }

    void Update(PlayField& world) override
    {
        pos.x += direction * velocity;
        // Border check
        if (pos.x < 0 || pos.x >= world.bounds.x - 1)
        {
            direction = -direction;
            pos.y += 1;
        }

        // Border check vertical:
        if (pos.y >= world.bounds.y - 1)
        {
            // kill player
            GameObject* player = world.GetPlayerObject();
            if (player && pos.IntCmp(player->pos))
            {
                //Spawn explosion
                GameObject& no = *(new Explosion);
                no.pos = pos;
                world.AddObject(&no);
                world.RemoveObject(player);
            }
        }

        // Transform into better Alien
        if (state!=as_Better)
        {
            floatRand updateRate(-maxUpdateRate, 2*maxUpdateRate);
            energy += updateRate(rGen);
            if (energy >= transformEnergy)
                Transform();
        }

        floatRand fireRate(0, 1);
        if (fireRate(rGen) < 0.5 && world.AlienLasers>0)
        {
            //Spawn laser
            GameObject& newLaser = *(new AlienLaser);
            newLaser.pos = pos;
            world.SpawnLaser(&newLaser);
        }
    }
};

class PlayerShip : public GameObject
{
public:
    PlayerShip() { m_objType = new char[64]; strcpy(m_objType, "ot_PlayerShip"); sprite = RS_Player; }
    ~PlayerShip() { delete m_objType; }

    void Update(PlayField& world) override
    {
        if (world.cotrollerInput->Left())
            pos.x -= 1;
        else if (world.cotrollerInput->Right())
            pos.x += 1;

        if (world.cotrollerInput->Fire() && world.PlayerLasers>0)
        {
            //Spawn laser
            GameObject& newLaser = *(new PlayerLaser);
            newLaser.pos = pos;
            world.SpawnLaser(&newLaser);
        }
    }
};

int main()
{
    rGen.seed(1);
    Vector2D size(80, 28);
    Renderer mainRenderer(size);
    PlayField world(size);

    intRand xCoord(0, (int)size.x-1);
    intRand yCoord(0, 10);

    // Populate aliens
    for (int k = 0; k < 20; k++)
    {
        Alien& a = *(new Alien);
        a.pos.x = (float)xCoord(rGen);
        a.pos.y = (float)yCoord(rGen);
        world.AddObject(&a);
    }
    // Add player
    PlayerShip& p = *(new PlayerShip);
    p.pos = Vector2D(40, 27);
    world.AddObject(&p);

        world.Update();
        {
            RenderItemList rl;
            for (auto it : world.GameObjects())
            {
                RenderItem a = RenderItem(Vector2D(it->pos), it->sprite);
                rl.push_back(a);
            }

            mainRenderer.Update(rl);
            // Sleep a bit so updates don't run too fast
            std::this_thread::sleep_for(std::chrono::milliseconds(1));
        }

    return 0;
}

此外,VS 提供以下信息:

我假设,指针(或对象)似乎之前在某处被清除,但我不知道如何跟踪它。提前致谢。

【问题讨论】:

  • 有什么方法可以使这个例子最小化?这是您希望我们调试的大量代码。
  • 另外,永远不要PlayerShip&amp; p = *(new PlayerShip);。如果您需要一个对象,只需使用PlayerShip p;PlayerShip p{}; 即可。做PlayerShip&amp; p = *(new PlayerShip); 只是自找麻烦(你正在泄漏内存)。
  • char* m_objType = nullptr; -- 为什么不是std::string?另外 -- 程序编译没有错误,但是立即崩溃 -- 程序编译成功仅意味着没有语法错误,并且链接器找到了您正在调用的所有函数。它与您是否有逻辑错误无关。
  • @ivanshv 你应该努力减少,如果不是消除原始指针的使用。使用容器(您似乎正在这样做)和智能指针,例如std::unique_ptr,如果它似乎适合您的用例,std::shared_ptr。在这个 C++ 时代,用户代码中不应该有 new[] 调用,但你的代码有它们。使用new[]std::vector 或类似容器取代。
  • for (auto it : world.GameObjects()) -- 未定义的行为,因为如果迭代器引用GameObjects,则无法在基于范围的for 循环中“调整”GameObjects(删除/添加条目)。正如给你的答案所说,最好有一个布尔值的成员变量,确定对象是否还活着。然后您可以使用something like this 擦除对象。

标签: c++ pointers exception memory dynamic


【解决方案1】:

当您在Playfield::Update 中遍历它时,您可以使用各种::Update 方法在gameObjects 中添加和删除对象。这是有保证的崩溃,因为它会使 for 循环中的隐式迭代器无效。

要解决这个问题,可以:

  • GameObject::Update 返回一个布尔值,表示是否可以删除对象。这需要您使用显式迭代器重写Playfield::Update 中的循环,以便您执行it = gameObjects.erase(it);。不过,这仍然不允许您添加新对象。
  • 将添加和删除推迟到游戏循环结束。添加一个markForAddition / markForRemoval 方法,该方法将从游戏世界中删除这些对象 Playfield::Update 已经完成。您需要做一些额外的记账,以确保您不会更新或绘制在同一循环中先前已删除的对象,但这是可以克服的。
  • 切换数据结构:如果您在某处移除对象,std::list 不会使其迭代器失效。不过,您仍然需要小心当前元素。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-01
    • 1970-01-01
    • 2021-12-19
    • 2020-09-12
    • 1970-01-01
    • 2018-05-19
    • 2023-04-02
    • 1970-01-01
    相关资源
    最近更新 更多