1

I am reading a structure named "Highscore" from a binary file. I print the read data in the console and it looks fine. After that, I insert them into a vector with the push_back method and when I'm done, I return the vector.

After the return line, I get the following error (translated from French): "Exception not handled at 0x00007FF6223FF017 in Project1.exe : 0xC0000005 : access violation while reading from 0xFFFFFFFFFFFFFFFF.".

Here is my code:

struct Highscore{
    string username;
    int points;
};

vector<Highscore> ReadFromHighscores(){

    fstream highscoresFiles;
    highscoresFiles.open("highscores.db", ios::in | ios::out | ios::binary | ios::app);

    highscoresFiles.seekg(0);

    Highscore output;
    vector<Highscore> highscores;

    highscoresFiles.read(reinterpret_cast<char *>(&output), sizeof(Highscore));
    cout << "Read : " << output.points << output.username << endl;
    Highscore temp;
    temp.points = output.points;
    temp.username = output.username;


    if (!highscoresFiles.eof()) {
        highscores.push_back(temp);
    }


    highscoresFiles.close();

    return highscores;
}
4
  • 6
    std::string is not suited to be read from or saved to a binary file. Don't do that Commented Dec 3, 2018 at 16:31
  • The problem is that a std::string is usually nothing more than a pointer and a size. And pointer are specific to a single process. You can't save and load pointers (which is what you're doing) using different processes, even if they were started from the same executable. Commented Dec 3, 2018 at 16:31
  • @Someprogrammerdude std::string is actually more complex than that, and sometimes reading it from a binary file might succeed. Commented Dec 3, 2018 at 16:41
  • 1
    @n.m. I suppose you are relating to short string optimization. Well, it's not necassarily always the case, since it's implementation defined, so "sometimes reading it from a binary file might succeed" is the best you can get Commented Dec 3, 2018 at 17:06

1 Answer 1

1

As having been pointed out, std::string contains a char* (which you are saving and restoring) to the real data. The actual data is not saved and the pointer you restore is causing the crash. To fix that you can create operators for streaming to/from files and dump the username c-string (including the terminating \0) and then write the points int raw. This should work as long as your usernames don't contain \0 in the middle of the string (unusual but valid).

#include <iostream>
#include <iomanip>
#include <vector>
#include <fstream>

struct Highscore{
    std::string username;
    int points;

    Highscore(const std::string& n, int p) : username(n), points(p) {}
    Highscore() : Highscore("",0) {}
    // operator to save a Highscore to file
    friend std::ofstream& operator<<(std::ofstream&, const Highscore&);
    // operator to read a Highscore from file
    friend std::ifstream& operator>>(std::ifstream&, Highscore&);
    // operator to print a Highscore on other ostreams (like std::cout)
    friend std::ostream& operator<<(std::ostream&, const Highscore&);
};

std::ofstream& operator<<(std::ofstream& os, const Highscore& hs) {
    os.write(hs.username.c_str(), hs.username.size()+1);
    os.write(reinterpret_cast<const char*>(&hs.points), sizeof(hs.points));
    return os;
}

std::ifstream& operator>>(std::ifstream& is, Highscore& hs) {
    std::getline(is, hs.username, '\0');
    is.read(reinterpret_cast<char*>(&hs.points), sizeof(hs.points));
    return is;
}
std::ostream& operator<<(std::ostream& os, const Highscore& hs) {
    os << std::setw(15) << std::left << hs.username << std::setw(8) << std::right << hs.points;
    return os;
}

int main() {
    // create "highscores.db"
    {
        std::vector<Highscore> highscores {
            {"MadBrother33", 234234},
            {"Barny", 14234},
            {"Bart", 1434}
        };
        std::ofstream out("highscores.db", std::ios::binary);
        for(auto& hs : highscores) {
            out << hs;
        }
    }
    // read "highscores.db"
    {
        std::vector<Highscore> highscores;
        {
            Highscore hs;
            std::ifstream in("highscores.db", std::ios::binary);
            while(in>>hs) {
                highscores.emplace_back(std::move(hs));
            }
        }
        for(auto& hs : highscores) {
            std::cout << hs << "\n";
        }
    }
}

Output

MadBrother33     234234
Barny             14234
Bart               1434
Sign up to request clarification or add additional context in comments.

2 Comments

Why not just output the length of the string prior to it instead of relying on null-termination?
That would work but I think just making sure that the name doesn't contain \0 is fine too ... and it saves a few bytes in the file :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.