简体   繁体   中英

std::vector Sprite segmentation fault

I post here after an attempt on a french forum called OpenClassrooms, but having no answers, I post here too.

So I warn you, I'm new in C++ and SFML so there is probably ten thousand mistakes, and it seems that the book I read was a really bad book, so I try to correct that with Bjarne Stroustrup's book.

My issue is the following :

I create projectiles when I press Enter or Space (there is two players on the same keyboard). And every time I press it, I create a copy of the projectile's sprite for the new projectile, and we put it into a std::vector<sf::Sprite> . The issue is when I launch the game, if the two players press their shoot key (Enter and Space) at the same time (I mean, as long as the first projectile is visible), the game will crash and show Segmentation Fault (core dumped) . To resolve this, I created two sprites (one for each player), and affect it for their projectiles. The problem is when their attack speed is huge, they can shoot their second projectile before the first disappear, so the collisions will have a problem, because there is the same sprite twice... and the first one will not work. So, to resolve this problem, I wanted to use a std::vector. By the way, I don't try to resolve this for only two players, I plan to add some more, so I need something which would work with 1000 players, for example (of course I won't do this with 1000 players, but if it works with that amount, it will also work for 5 players).

To create my projectile, I use a reference to the object Sprite that I show later thanks to a method in my class Game. This reference is the reference to a sprite in the std::vector. I also realized that if we shoot a first projectile, wait for it to disappear and then let the two players shoot, it works properly (sometimes, it crashes sometimes too)... I don't understand why, but it's mostly when I start the game that it crashes.

Here is my code :

std::vector<sf::Sprite> sprites;

int main()
{
    Game game;

    sf::ContextSettings settings;
    settings.antialiasingLevel = 8;
    sf::RenderWindow window(sf::VideoMode(1600, 900), "Bombardes", sf::Style::Default, settings);
    sf::Texture text;
    if (!text.loadFromFile("resources/projectile.png")) {
        logg.error("Could not create texture for projectile. Aborting.");
    }
    Bombard bomb(50, 150, &game, &pSprite, &movement2); // player class
    Bombard bomb2(1550, 850, &game, &pSprite2, &movement);
    std::vector<std::shared_ptr<Projectile>> p;
   while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;
                case sf::Event::KeyPressed:
                    if (event.key.code == sf::Keyboard::Return) { // Player 2
                        auto current2 = std::chrono::steady_clock::now();
                        auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(current2 - last2);
                        if (elapsed2.count() >= 1000 / bomb2.getAttackSpeed()) { // Time the frequency of shots
                            last2 = std::chrono::steady_clock::now();
                            if (bomb2.getAmmo() > 0) { // Check if there's still ammo
                                sprites.push_back(sf::Sprite(text)); // New sprite in vector
                                p.push_back(std::make_shared<Projectile>(bomb2.getPos().getX(), bomb2.getPos().getY(), &sprites[sprites.size()-1], &game, bomb2.getProjectileMovement(), bomb2.getPenetration(),
                                bomb2.getSpeed())); // Create the projectile
                                bomb2.fire(); // Remove an ammo
                            }
                        }
                    }
                    else if (event.key.code == sf::Keyboard::Space) { // Player 1
                        auto current = std::chrono::steady_clock::now();
                        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current - last);
                        if (elapsed.count() >= 1000 / bomb.getAttackSpeed()) {
                            last = std::chrono::steady_clock::now();
                            if (bomb.getAmmo() > 0) {
                                sprites.push_back(sf::Sprite(text));
                                p.push_back(std::make_shared<Projectile>(bomb.getPos().getX(), bomb.getPos().getY(), &sprites[sprites.size()-1], &game, bomb.getProjectileMovement(), bomb.getPenetration(), bomb.getSpeed()));
                                bomb.fire();
                            }
                        }
                    }
                    break;
            }
        }
    }
    return 0;
}

Of course, I removed some parts, I wouldn't copy paste my whole code, there are useless things. If you feel like you need my classes (Projectile, Game, Entity, Bombard), I'll post them.

I guess it can help you to see the Projectile's constructor :

Projectile::Projectile(int posX, int posY, sf::Sprite *sprite, Game *game, sf::Vector2f direction, double penetration, double speed) {
    /** @brief Full constructor.
    @param int posX : The X position on the map.
    @param int posY : The Y position on the map.
    @param sf::Sprite *sprite : A pointer to the sprite.
    @param Game *game : A pointer to the game.
    @param sf::Vector2f direction : The direction in which the projectile will move. */ }

Thanks for your help !

There's already quite a lot going on, so I might have missed a few things.

One thing seems odd: both player are using the same Projectile pointer when they shoot!

Player 1 shoots: a first projectile is heap allocate and you keep its address in p . So far so good.

Then Player 2 shoots. You create a new projectile (with the correct position and sprite and so on...) BUT, you also store its address in p .

Unless you have saved the first projectile's address somewhere else in your code, then how do you manage to access it? How can you know if it has reached its target (and then, Player 1 should score) or if has gone outside the screen (and then you can delete it to clear up memory) ?

I suspect there is something around it. Maybe you should try storing all your projectiles in a std::vector<Projectile*> or even better, a std::vector<std::unique_ptr<Projectile>> . This way (if I did understand the code right) player might be able to shoot more than one projectile.

(if you are wondering about that unique_ptr part, don't mind asking)

Keep us informed of what you've tried, will you?

Okay, so, after a day of work on it, thanks to giant_teapot help, I think I finally resolved the issue. But I'm not sure. I don't experiment this issue anymore, but I don't understand why.

Instead of using raw pointers, I decided to use smart pointers. It seems that there is no more bug. But why ? I changed strictly nothing. There is something I don't understand. But here's the changed code :

std::vector<sf::Sprite> sprites;

int main()
{
    Game game;

    sf::ContextSettings settings;
    settings.antialiasingLevel = 8;
    sf::RenderWindow window(sf::VideoMode(1600, 900), "Bombardes", sf::Style::Default, settings);
    sf::Texture text;
    if (!text.loadFromFile("resources/projectile.png")) {
        logg.error("Could not create texture for projectile. Aborting.");
    }
    Bombard bomb(50, 150, &game, std::make_shared<sf::Sprite>(pSprite), &movement2);
    Bombard bomb2(1550, 850, &game, std::make_shared<sf::Sprite>(pSprite2), &movement);
    std::vector<std::shared_ptr<Projectile>> p;
   while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            switch (event.type) {
                case sf::Event::Closed:
                    window.close();
                    break;
                case sf::Event::KeyPressed:
                    if (event.key.code == sf::Keyboard::Return) { // Player 2
                        auto current2 = std::chrono::steady_clock::now();
                        auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(current2 - last2);
                        if (elapsed2.count() >= 1000 / bomb2.getAttackSpeed()) { // Time the frequency of shots
                            last2 = std::chrono::steady_clock::now();
                            if (bomb2.getAmmo() > 0) { // Check if there's still ammo
                                sprites.push_back(sf::Sprite(text)); // New sprite in vector
                                p.push_back(std::make_shared<Projectile>(bomb2.getPos().getX(), bomb2.getPos().getY(), std::make_shared<sf::Sprite>(sprites[sprites.size()-1]), &game, bomb2.getProjectileMovement(), bomb2.getPenetration(), bomb2.getSpeed())); // Create the projectile
                                bomb2.fire(); // Remove an ammo
                            }
                        }
                    }
                    else if (event.key.code == sf::Keyboard::Space) { // Player 1
                        auto current = std::chrono::steady_clock::now();
                        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current - last);
                        if (elapsed.count() >= 1000 / bomb.getAttackSpeed()) {
                            last = std::chrono::steady_clock::now();
                            if (bomb.getAmmo() > 0) {
                                sprites.push_back(sf::Sprite(text));
                                p.push_back(std::make_shared<Projectile>(bomb.getPos().getX(), bomb.getPos().getY(), std::make_shared<sf::Sprite>(sprites[sprites.size()-1]), &game, bomb.getProjectileMovement(), bomb.getPenetration(), bomb.getSpeed()));
                                bomb.fire();
                            }
                        }
                    }
                    break;
            }
        }
    }
    return 0;
}

You'll understand that I also changed my classes. But it still doesn't explain why raw pointers make the whole thing crash while smart pointers work properly.

Again, a huge thanks giant_teapot who helped me during many hours and showed me these wonderful pointers. Also thanks Martin Bonner who told him to show these to me.

EDIT : After some changed in my code, I realized why make_shared is working whereas raw pointers aren't working. I didn't really understand how to use smart pointer to be honest, it was something like "random stuff written here because it works". But finally, the problem isn't really solved. We don't really access the vector. This code creates a copy of my sprite and puts its address in the shared_pointer. BUT if in the projectile class, we modify the sprite, we don't modify the sprite in the vector, but the copy. SO it solves the problem by implementing another problem : what's the use of the vector, if its members are useless because copied ?

For the ones who are wondering how to solve the vector problem, I have actually no clue, the problems seems to be a vector problem, not SFML. I probably need to study them more. So my code works, but is something really bad. At least, it helped me learning the use of smart pointer and std::make_shared.

EDIT 2 : After walking on the Internet trying to search how to access the address of a vector member, it find a post on SO, and realized I wasn't thinking bad. I could use iterator but it wasn't the thing I searched. And now I totally understand why my program didn't work. Depending on how the game was going, it could or couldn't crash, but it was this undefined behaviour which was problematic. To be honest, I had no idea on how to use vectors, it was really new to me, I was experimenting things I wasn't mastering. By reading the post on SO, I learnt that accessing vector addresses is dangerous. If the vector size changes and becomes higher than the allocated size, elements might move, making the pointer invalid. That's probably what happens when I pressed Space + Enter, creating two projectiles. It was just about luck. So I have to find something to bypass this behaviour. Making a copy is a solution, but there are many others, I'll dig the subject.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM