Мой отладчик говорит, что у меня ошибка Segfault, но я не могу сказать, где, происходит в очень определенных обстоятельствах - PullRequest
1 голос
/ 28 марта 2019

Мой отладчик (gdb) говорит, что у меня ошибка Segfault, но не могу сказать, где она, говорит ?? () Для функции.

Я начал получать сообщение об ошибке в очень специфической ситуации, когда я щелкаю по элементу типа оружия, а затем щелкаю по другому пригодному для использования элементу, где моя программа дает сбой. Я удалил большинство указателей из своего кода, потому что сначала думал, что это проблема, но это ничего не изменило. Я не смог найти подобную ошибку в сети, и отладка оказалась не очень полезной, потому что она не может сказать мне, откуда именно эта ошибка.

Relavant Player.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, затем в itemsList она должна уменьшить количество (значение карты), а если оно <= 0, оно должно полностью его удалить. UpdateButtons очищает весь вектор кнопок и создает новые из itemsList, по одной кнопке для каждой клавиши. Класс Item имеет перечисление для какого типа предмета, меч (ID предмета 0) - это оружие, зелье (ID предмета 1) - предмет, который можно использовать, когда я нажимаю на меч (в настоящее время ничего не делает), затем добавьте полезный предмет (зелье) и нажмите на новую кнопку, которая была создана для использования, есть ошибка. Этого не произойдет, если я сначала не нажму кнопку меча, ни в одном другом случае не произойдет ошибка, когда я использую все предметы, затем добавлю больше в свой инвентарь и использую их все снова. Я подозреваю, что это ошибка с тем, как я обновляю кнопки и удаляю ключи, но я не могу найти это. Все типы элементов находятся в одном и том же векторе кнопок, и тип элемента в значительной степени определяет только то, где будет расположена кнопка. </p>

1 Ответ

1 голос
/ 28 марта 2019

В 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
}
...