-1

I am currently on the path of learning C++ and this is an example program I wrote for the course I'm taking. I know that there are things in here that probably makes your skin crawl if you're experienced in C/C++, heck the program isn't even finished, but I mainly need to know why I keep receiving this error after I enter my name: Exception thrown at 0x79FE395E (vcruntime140d.dll) in Learn.exe: 0xC0000005: Access violation reading location 0xCCCCCCCC. I know there is something wrong with the constructors and initializations of the member variables of the classes but I cannot pinpoint the problem, even with the debugger. I am running this in Visual Studio and it does initially run, but I realized it does not compile with GCC. Feel free to leave some code suggestions, but my main goal is to figure out the program-breaking issue.

#include <iostream>
#include <string>
#include <cstdio>
#include <array>
#include <cstdlib>
#include <ctime>

int getRandomNumber(int min, int max)
{
    static constexpr double fraction{ 1.0 / (RAND_MAX + 1.0) };
    return min + static_cast<int>((max - min + 1) * (std::rand() * fraction));
}

class Creature
{
protected:
    std::string m_name;
    char m_symbol;
    int m_health;
    int m_damage;
    int m_gold_count;
public:
    Creature(const std::string name, char symbol, int health, int damage, int gold_count) :
        m_name{ name }, m_symbol{ symbol }, m_health{ health }, m_damage{ damage }, m_gold_count{ gold_count }
    {}
    const std::string getName() const { return m_name; }
    const char getSymbol() const { return m_symbol; }
    const int getHealth() const { return m_health; }
    const int getDamage() const { return m_damage; }
    const int getGold() const { return m_gold_count; }
    void reduceHealth(int hp_taken) { m_health -= hp_taken; }
    bool isDead() const { return m_health <= 0; }
    void addGold(int gold) { m_gold_count += gold; }
};

class Player : public Creature
{
private:
    int m_level = 1;
public:
    Player(const std::string name) : Creature{ name, '@', 10, 1, 0}
    {}
    int getLevel() { return m_level; }
    void levelUp() { ++m_level; ++m_damage; }
};

class Monster : public Creature
{
public:
    enum class Type
    {
        dragon,
        orc,
        slime,
        max_types
    };
private:
    static const Creature& getDefaultCreature(const Type& type)
    {
        static const std::array<Creature, static_cast<std::size_t>(Type::max_types)> monsterData{
        { {"dragon", 'D', 20, 4, 100}, {"orc", 'o', 4, 2, 25}, {"slime", 's', 1, 1,10}}
        };

        return monsterData.at(static_cast<std::size_t>(type));
    }
public:
    Monster(const Type& type) : Creature{getDefaultCreature(type)}
    {
    }
    static const Monster& getRandomMonster() {
        return Monster(static_cast<Type>(getRandomNumber(0, static_cast<int>(Type::max_types)-1)));
    }
};
void attackPlayer(const Monster& monster, Player& player) {
    std::cout << "The " << monster.getName() << " hit you for " << monster.getDamage() << ".\n";
    player.reduceHealth(monster.getDamage());
}

void attackMonster(Monster& monster, Player& player) {
    std::cout << "You hit the " << monster.getName() << " for " << player.getDamage() << ".\n";
    monster.reduceHealth(player.getDamage());
    if (monster.isDead()) {
        printf("The %s is dead!\n", monster.getName());
        player.levelUp();
        player.addGold(monster.getGold());
        if (player.getLevel() == 20)
            printf("You won!");
        return;
    }
    attackPlayer(monster, player);
}
void fightMonster(Player& player) {
    Monster m{ Monster::getRandomMonster() };
    printf("You have encountered a %s (%c).\n", m.getName(), m.getSymbol());
    while (!m.isDead() && !player.isDead()) {
        std::cout << "(R)un or (F)ight: ";
        char choice;
        std::cin >> choice;
        if (choice == 'r' || choice == 'R') {
            if (getRandomNumber(1, 2) == 1) {
                std::cout << "You have successfully fled.\n";
                return;
            }
            else {
                std::cout << "You failed to flee.\n";
                attackPlayer(m, player);
            }
        }
        else if (choice == 'f' || choice == 'F')
        {
            attackMonster(m, player);
        }
    }
}

int main()
{
    std::srand(time(NULL));
    std::rand();
    std::string name;
    std::cout << "Enter Your Name: ";
    std::cin >> name;
    Player p{ name };
    while (!p.isDead() && p.getLevel() < 20)
    {
        fightMonster(p);
    }
    if (p.isDead())
        printf("You have died with %d gold and are level %d.", p.getGold(), p.getLevel());
}
unkn0wn.dev
  • 235
  • 2
  • 8
  • 1
    I'd recommend breaking the problem down into a [mre]. This step can often lead to solving your own problem. – ChrisMM Jun 13 '21 at 00:44
  • 1
    CCCCCCCC represents _Used by Microsoft's C++ debugging runtime library and many DOS environments to mark uninitialized stack memory. CC resembles the opcode of the INT 3 debug breakpoint interrupt on x86 processors._ – ChrisMM Jun 13 '21 at 00:46
  • @ChrisMM Thanks for your feedback. I would've tried and shortened this, but I didn't know if shortening it would leave something that I need to display out of the example. – unkn0wn.dev Jun 13 '21 at 01:11

1 Answers1

4

The problem is here:

static const Monster& getRandomMonster() {
    return Monster(static_cast<Type>(getRandomNumber(0, static_cast<int>(Type::max_types)-1)));

Monster(...) creates a temporary which is destroyed at the end of the statement in which it is created. You are therefore returning a 'dangling' reference. To fix this, just return by value:

static Monster getRandomMonster() {
    return Monster(static_cast<Type>(getRandomNumber(0, static_cast<int>(Type::max_types)-1)));

With a suitable warning level set, your compiler should warn you about this.

Also, this is wrong:

printf("The %s is dead!\n", monster.getName());

It should be:

printf("The %s is dead!\n", monster.getName().c_str());

Again, the compiler should warn you about this.

Style notes:

  • don't mix calls to printf and cout. Choose one and stick to it (cout is idiomatic for C++ programs).

  • don't specify const when returning by value. It has no effect and (with the right compiler flags) generates compiler warnings. You might, however, want to mark the functions as const, since they don't mutate the object they are called on: return_type foo (blah) const { ... }

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Thank you very much and I do know to use the c_str method when using printf, I just got kind of annoyed trying to figure this out. Now I this poses another question. I originally had my member variables as string_views and the program compiled and worked fine. – unkn0wn.dev Jun 13 '21 at 01:09
  • Also I'm sure you may be able to agree that printf is sometimes handy with string formatting instead of chaining everything with cout. – unkn0wn.dev Jun 13 '21 at 01:12
  • The thing about UB is that it makes the behaviour of your program unpredictable. `std::string_view` requires the pointed-to string to remain in existence while the `string_view` is alive. I don't think that's the case here - you are passing your `string` parameters by value so they will go out of scope when the functions they are passed to return, so I guess you just got lucky (or maybe unlucky). As for using `printf`, I have a certain fondness for it myself, yes. – Paul Sanders Jun 13 '21 at 01:16
  • Sorry, what is UB? Also I see what you are saying now. – unkn0wn.dev Jun 13 '21 at 01:23
  • Sorry, jargon. It's [undefined behaviour](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior) – Paul Sanders Jun 13 '21 at 01:35
  • Ah of course it does. Also I am trying to set up the Visual Studio compiler to throw a warning when I return that dangling reference, but even with all errors on it does not. – unkn0wn.dev Jun 13 '21 at 01:37
  • Never mind, after messing around I finally got it. It does seem as it should show up as a normal warning without having to go to a deeper warning level. – unkn0wn.dev Jun 13 '21 at 01:41
  • Aim to have your code compile warning-free. Then, important information won't get lost in the noise. – Paul Sanders Jun 13 '21 at 01:43