1

Let there be a C++ struct S of which an instance is created and written to a binary file. Then said file is read back into the program and an instance should be created from it. How is this second part achieved?

Here is the definition of S:

struct S {
    int value;
    string txt;
};

Now to create a binary file with the data for one such struct, the following function is used:

int writeStructAsBinary() {
    ofstream ofs;

    S s;
    s.txt = "Hello World";
    s.value = 10;

    ofs.open("output.dat", ios::out | ios::binary);
    if (!ofs.good()) { cerr << "Error opening file." << endl; return 1; }
    ofs.write((char*)&s, sizeof(S));
    ofs.close();
    return 0;
}

I'm a bit unsure if the (char*)&s is correct but that's what I got.

Now how can I read the data from the output.dat file back into the program? In principle it should just be "read sizeof(S) bytes", but how do I create a Struct from the bytes? Here's my attempt?

int readAndPrintBinary() {
    ifstream ifs;
    ifs.open("output.dat", ios::in | ios::binary);
    if (!ifs) { cerr << "Could not open file." << endl; return 1; }
    
    
    ifs.seekg(0, ios::beg);
    char* binaryInstance = new char[sizeof(S)];
    ifs.read(binaryInstance, sizeof(S));
    ifs.close();
    
    delete binaryInstance;
    return 0;
}

I'd like to now confirm s.txt and s.value. How can I do that?

The main program is this:

#include <iostream>
#include <string>
#include <sstream>
#include <string>
#include <fstream>

using namespace std;

int main() {
    writeStructAsBinary();
    readAndPrintBinary();
}

Using reinterpret_cast to recreate the object:

int readAndPrintBinary() {
    ifstream ifs;
    ifs.open("output.dat", ios::in | ios::binary);
    if (!ifs) { cerr << "Could not open file." << endl; return 1; }
    
    ifs.seekg(0, ios::beg);
    char* binaryInstance = new char[sizeof(S)];
    ifs.read(binaryInstance, sizeof(S));
    S* s = reinterpret_cast<S*>(binaryInstance);
    ifs.close();
    
    cout << "s->txt = " << s->txt << endl;
    cout << "s->value = " << s->value << endl;

    delete[] binaryInstance;
    return 0;
}
TMOTTM
  • 3,286
  • 6
  • 32
  • 63
  • This code will never work as shown, because you can't serialize `std::string` members in this manner. – Remy Lebeau Jan 04 '21 at 22:23
  • Unless you have a _POC_ type, that's unlikely to work. One for sure, it doesn't work with `std::string` members that way. – πάντα ῥεῖ Jan 04 '21 at 22:23
  • 1
    You need to define the format you're going to use to store the data in the file. Files are streams of bytes -- you cannot read and write data to and from a file until and unless you define some way to represent the data as a stream of bytes. There is no guarantee that the computer is already storing the data as a stream of bytes and, in most cases, it will not be. – David Schwartz Jan 04 '21 at 22:27
  • I'll change the example to contain only the int and a char array. that should make it a POD, correct? – TMOTTM Jan 04 '21 at 22:28
  • @TMOTTM `ofs.write((char*)&s, sizeof(S));` -- This can't work -- think about it. If you had a thousand characters in that `std::string`, the `sizeof(S)` would still be, I don't know, maybe a few bytes. So what magic would be used to write those thousands of characters to a file, when you've only specified, maybe 48 bytes to write? And then what magic is going to be used to turn those 48 bytes from that file into a string of 1000 characters? – PaulMcKenzie Jan 04 '21 at 22:35
  • @RemyLebeau I just tried with the suggestion of Tumbleweed below and it does exactly what I was hoping for. I added the extened function to the post. – TMOTTM Jan 04 '21 at 22:35
  • @PaulMcKenzie Understood. The string is a problem, but appart from that, is the mechanics of re-creating an object as shown here going in the right direction? – TMOTTM Jan 04 '21 at 22:37
  • @TMOTTM it is better to not serialize manually at all. There are plenty of serialization libraries available to handle this for you. But if you must serialize manually, there are ways to optimize it without using an intermediate byte array. – Remy Lebeau Jan 04 '21 at 22:38
  • `char* binaryInstance = new char[sizeof(S)];` -- This still has a problem if `S` is not a POD type. Are you aware of what `sizeof` does? – PaulMcKenzie Jan 04 '21 at 22:39
  • @PaulMcKenzie `sizeof(S)` should give me the size in bytes of one instance of the struct. But the way you're asking makes me feel that's wrong. – TMOTTM Jan 04 '21 at 22:45
  • @TMOTTM [See this example](http://coliru.stacked-crooked.com/a/6c0e1d17c5ea4dec). The `sizeof` is a compile-time constant. It has no idea how many characters are in the `std::string` at runtime. So there is no way `sizeof(S)` would have worked in your attempt if `S` still had the `std::string` member. – PaulMcKenzie Jan 04 '21 at 23:20

1 Answers1

1

You can't do this if S has a member of non-POD type, which it does: std::string. It's going to invoke undefined behavior, and almost certainly crash.

If S were a POD type, you'd re-create the object by doing:

S *s = reinterpret_cast<S*>(binaryInstance);

You then have to make sure you never delete s, but only call delete[] binaryInstance;

Remember that structs may have padding as well, so you may be writing out a bunch of useless padding bytes (which in the worst case may contain junk from the heap that could lead to security implications for your program!!)

Your much safer alternative here is to just persist the members of S directly, one by one. It's more code, yes, but much safer and more portable.

Tumbleweed53
  • 1,491
  • 7
  • 13