首页 > 解决方案 > 我的调试器说我有一个 Segfault 错误,但不能告诉我在哪里,发生在非常具体的情况下

问题描述

我的调试器 (gdb) 说我有一个段错误,但不能告诉我它在哪里,说 ?? () 为函数。

我在一个非常特殊的情况下开始出现错误,我点击一个武器类型项目,然后点击另一个我的程序段错误的可用项目。我从我的代码中删除了大多数指针,因为起初我认为这是问题所在,但它并没有改变任何东西。我在网上找不到类似的错误,调试也不是很有帮助,因为它无法告诉我错误的确切来源。

相关播放器.h

class Player{
private:
    int HP;     //player's Health Points
    int SP;     //player's Special Points
    int maxHP;  //maximum value for the player's health points
    int maxSP;  //maximum value for the player's special points
    uint32_t money; //how much money the player has

    float speed; //speed the player will move at (pixels per frame)

    bool menuOpen;  //boolean which is true when the inventory Menu is open

    sf::Vector2f pos; //player's position

    //tests if the player is colliding with any items
    //in the room, accepts the memory address of the room
    bool checkForItems(Room* r);

    std::vector<int> ItemInventory;         //the player's inventory of items.
    std::vector<TextButton> itemButtons;    //vector of TextButtons that correspond with each unique item in the player's inventory
    std::map<std::string, int> ItemsList;   //map that stores each unique item's name and it's quantity

    Control openMenu;           //control to open the menu

    void ItemAttributes(int id);        //makes the items do something depending on what was passed
    bool removeItem(int id);            //removes an item from the player's inventory
    void useItem(Item it);             //takes an Item* and passes it's ID to ItemAttributes
    void updateButtons();               //updates the buttons based on the player's inventory

    Item* EquipedWeapon;    //stores the player's currently equipped weapon

    //enum to store the player's direction for rotations
    enum direction {
        up,
        down,
        left,
        right
    } currentDirection;

    void setRotation();
public:
    //constructor for the player, initializes maxHP, maxSP, and their position:
    Player(int maxhp, int maxsp, sf::Vector2f position);

    //getters:
    int ItemButtonsTextSearchC(const std::wstring& text);   //returns the index in itemButtons for a TextButton which *contains* the text parameter
    int ItemButtonsTextSearch(const std::wstring& text);   //returns the index in itemButtons for a TextButton which equals the text parameter

    //setters:
    void setMoney(int amount);  //sets the money variable
    void setHP(int hp);         //sets the HP variable
    void setSP(int sp);         //sets the SP variable

    void menu();    //inventory menu, handles all things related to the player's inventory
    void draw();    //draws the player to the screen
};

Player.cpp 中导致问题的方法:

void Player::menu(){
    //iterate through all the items listed in itemsList:
    for(auto& p : ItemsList){
        //get a pointer to the item by it's name:
        Item item = *Materials::getItemFromName(p.first);

        //convert the item's name to a wstring for easier use in parameters:
        std::wstring wItemName = std::wstring(p.first.begin(), p.first.end());

        //get the index of the item's corresponding button
        //by searching the itemButtons vector for a button
        //whose text matches this item's name:
        int bIndex = ItemButtonsTextSearch(wItemName);

        if(bIndex != -1){
            //store the button's position:
            sf::Vector2f buttonPosition = itemButtons.at(bIndex).getPosition();

            //default the Y values to 150, we need a separate Y for each one
            //because there are 4 columns of buttons for each type of item
            float weaponY = 150, usableY = 150, collectibleY = 150, moneyY = 150;

            //switch statement to determine the position of the button:
            switch(item.getType()){
                case Item::Weapon:
                    buttonPosition.x = 100;
                    buttonPosition.y = weaponY;

                    //increment by 20 to give space between buttons:
                    weaponY += 20.0f;
                    break;
                case Item::Usable:
                    buttonPosition.x = 375;
                    buttonPosition.y = usableY;

                    //increment by 20 to give space between buttons:
                    usableY += 20.0f;
                    break;
                case Item::Collectible:
                    buttonPosition.x = 650;
                    buttonPosition.y = collectibleY;

                    //increment by 20 to give space between buttons:
                    collectibleY += 20.0f;
                    break;
                case Item::Money:
                    buttonPosition.x = 925;
                    buttonPosition.y = moneyY;

                    //increment by 20 to give space between buttons:
                    moneyY += 20.0f;
                    break;
            }

            //set the button's position now that it's X has been determined:
            itemButtons.at(bIndex).setPosition(buttonPosition);

            /*
            * below we will set the button's text to represent
            * it's corresponding item's name as well as it's
            * quantity then draw the button to the screen so
            * that the client can see how many of each item
            * they have, but then we change it back so that it
            * doesn't break any comparisons with the button's
            * Text (ItemButtonsTextSearch for example):
            */

            //text representing item's quantity to append to the end of the the item's name:
            std::string QuantityText = "\tx" + std::to_string(p.second);

            //wide string that will be the button's text:
            std::wstring wQText = wItemName + std::wstring(QuantityText.begin(), QuantityText.end());

            //set the button's text (it takes wchar_t* so we call .c_str() on wQText):
            itemButtons.at(bIndex).setText(wQText.c_str());

            //draw the button with the temporary text to the screen:
            itemButtons.at(bIndex).draw();

            //poll if the button was clicked, and if it was,
            //we will call useItem on it's corresponding Item:
            if(itemButtons.at(bIndex).pollClicked()){
                useItem(item);
            }

            //change the button's text back to what it was, note: there
            //is a possibility of the button being removed after calling
            //useItem() because when an item's quantity hits 0, the
            //button corresponding with that item is removed, therefore
            //we need a check after the useItem() call to make sure that
            //we don't get an index out of bounds error:
            if(ItemButtonsTextSearchC(wQText) != -1)
                itemButtons.at(bIndex).setText(wItemName.c_str());
        }
    }
}
void Player::useItem(Item it){
    int itemID = it.getItemID();
    ItemAttributes(itemID);
}
void Player::ItemAttributes(int id){
    switch(id){
        case 0: //sword
            //EquipedWeapon = Materials::getItem(id);
            break;
        case 1: //ultra potion of healing
            healHP(50);
            removeItem(id);
            break;
    }
}
bool Player::removeItem(int id){
    //this will be set to true as soon as we find the item:
    bool found = false;

    //loop through ItemInventory and remove the first occurance of id
    //if it exists, otherwise found will remain false:
    for(int i = 0; i < ItemInventory.size(); i++){
        if(ItemInventory.at(i) == id){
            ItemInventory.erase(ItemInventory.begin() + i);
            found = true;
            break;
        }
    }

    //if the item was not found in the inventory, there is no need to
    //continue, we can just return false because we know that it isn't
    //in the player's inventory so it can't be removed in the first place:
    if(!found)
        return false;

    //get an iterator for the item ID's corresponding name in itemsList:
    auto itr = ItemsList.find(Materials::itemNames[id]);

    //check to make sure the item is actually listed; it will be
    //but this is a safeguard in case something breaks:
    if(itr != ItemsList.end()){
        //decrement the item's quantity:
        itr->second--;

        //if there are none remaining, we remove it from the itemsList entirely:
        if(itr->second <= 0)
            ItemsList.erase(itr);
    }

    //update the buttons based on the new changes:
    updateButtons();

    //return true because if it got to this point,
    //the item was found and removed:
    return true;
}
int Player::ItemButtonsTextSearchC(const std::wstring& text){
    if(itemButtons.size() > 0){
        for(int i = 0; i < itemButtons.size(); i++){
            std::wstring bTxt = itemButtons.at(i).getText();
            if(bTxt.find(text) != std::string::npos)
                return i;
        }
    }

    return -1;
}
int Player::ItemButtonsTextSearch(const std::wstring& text){
    if(itemButtons.size() > 0){
        for(int i = 0; i < itemButtons.size(); i++){
            std::wstring bTxt = itemButtons.at(i).getText();
            if(bTxt == text)
                return i;
        }
    }

    return -1;
}
void Player::updateButtons(){
    //first we clear the vector of itemButton:
    itemButtons.clear();

    //loop to go through each unique item in ItemsList map
    //and make a button for each one:
    for(auto& p : ItemsList){
        //convert the item's name into a wstring (textbutton constructor takes wchar_t*, wstring is easier to work with):
        std::wstring wName = std::wstring(p.first.begin(), p.first.end());

        //add the new button to itemButtons
        TextButton btn(sf::Vector2f(0, 0), sf::Color::Magenta, sf::Color::White, wName.c_str(), 18);

        //make sure button presses only register once
        btn.setWasClicked(true);

        //add the button to the itemButtons vector:
        itemButtons.push_back(btn);
    }
}
bool Player::checkForItems(Room* r){
    //itemIndex will == the ID of any item we collided with, if there
    //was no item it returns -1
    int itemIndex = r->checkForItemCollision(player.getGlobalBounds());

    if(itemIndex >= 0){
        //get item ID from the item we just collided with:
        int itemID = r->getItem(itemIndex).collect();

        //remove the item from the room and add it's ID to ItemInventory:
        r->removeItem(itemIndex);
        ItemInventory.push_back(itemID);

        //get the item's name and add it to itemsList if it doesn't exist.
        std::string itemName = Materials::itemNames[itemID];

        //if the item's name is listed in itemsList, we increment it's
        //quantity, else we add it and initialize it's quantity to 1:
        if(ItemsList.count(itemName) != 0){
            ItemsList.at(itemName)++;
        } else {
            ItemsList.insert(std::make_pair(itemName, 1));
        }

        //update the buttons in case a new item was obtained:
        updateButtons();

        //return true because item was found:
        return true;
    }

    //return false, item wasn't found:
    return false;
}

当我点击一个按钮时,它应该从 itemInventory 中删除该项目的数字 ID 的第一次出现,然后在 itemsList 中它应该减少数量(地图的值),如果它 <= 0,它应该完全删除它。UpdateButtons 清除整个按钮向量并从 itemsList 中创建新的按钮,每个键对应一个按钮。Item 类有一个关于它是什么类型的项目的枚举,剑(项目 ID 0)是武器,药水(项目 ID 1)是可用项目,当我点击剑时(当前什么都不做),然后添加一个可用的项目(药水)并单击为可用而创建的新按钮,它有一个错误。除非我首先单击剑的按钮,否则不会发生这种情况,在任何其他情况下,当我使用所有项目时它都不会出错,然后将更多项目添加到我的库存并再次使用它们。我怀疑它' 我更新按钮和删除键的方式出错,但我找不到。所有的项目类型都在同一个按钮向量中,项目类型几乎只决定了按钮的位置。

标签: c++segmentation-fault

解决方案


Player::menu中,您有一个循环,for(auto& p : ItemsList)。在该循环中,您调用一个序列 ( useItem(item)-> ItemAttributes(itemID)-> removeItem(id)-> ItemsList.erase(itr)) 可以修改ItemsList您正在迭代的地图。这会使当前引用的迭代器无效p,因此当您尝试访问映射中的下一个元素时,您会得到未定义的行为,因为(内部使用的)迭代器不再有效。

一种可能的补救措施是更改for循环以使用您自己的迭代器,并在循环体顶部增加迭代器(在修改映射之前):

for (auto it = ItemsList.begin(); it != ItemsList.end(); ) {
     auto &p = *it;
     ++it;
     // rest of for loop
}

推荐阅读