Home > Software engineering >  Read/write a vector of Structs into a binary file and reading a vector of structs from a file in C
Read/write a vector of Structs into a binary file and reading a vector of structs from a file in C

Time:12-07

So i have a struct called info that contains different kind of variable and i created a vector inside of this struct. now i want to save this vector into a binary file so i can read it later. My code so far

    #include <iostream>
#include <fstream>
#include <vector>
#include <cstring>
struct MyStruct
{
    int a;
};
struct info
{
    char name[30];
    int age;
    char bbl[20];
    std::vector<MyStruct> my;
};
void create(std::vector<info>& test)
    {
        std::ofstream file("info.dat", std::ios::binary | std::ios::app); // output file stream

        // dump the contents of the vector in the file
        for (const info& inf : test)
            file.write(reinterpret_cast<const char*>(&inf), sizeof(inf));
    }

int main()
{
    info info1;
    // create a test vector
    std::vector<info> test;
    info1.age = 3443;
    std::cin >> info1.name;
    std::cin >> info1.bbl;
    MyStruct my;
    my.a = 4;
    info1.my.push_back(my);

    test.push_back(info1);
    
    std::cout << '\n';
    create(test);
    
    test.clear(); // clear the vector

    {
        std::ifstream file("info.dat", std::ios::binary); // input file stream

        // read the contents of the file into the vector
        info inf;
        while (file.read(reinterpret_cast<char*>(&inf), sizeof(inf)))
            test.push_back(inf);
    }

    // print out the contents of test
    for (int i = 0; i < test.size(); i  )
        std::cout << "{ " << test[i].name << ", " << test[i].age << ", " << test[i].bbl << " } ";
    std::cout << '\n';
}

But i got error on while (file.read(reinterpret_cast<char*>(&inf), sizeof(inf))) which is

Exception thrown at 0x7AD63E9E (vcruntime140d.dll) in ConsoleApplication9.exe: 0xC0000005: Access violation reading location 0xCC007364.

How can i fix that?

CodePudding user response:

The std::vector object itself probably does not contain any actual data. Instead, it probably only contains bookkeeping information, for example

  • the number of valid elements,
  • the maximum number of elements for which memory has been allocated, and
  • a pointer to the start of the actual data.

Therefore, simply dumping the contents of the std::vector object to file will not be useful.

If you want to write the actual data of a std::vector to file, you must first decide how it should be stored in the file. One simple way of storing it would be to first write the number of elements as an int to the file, and then to write all of the individual elements to the file one after another. That way, when reading the file later, the reading function can easily find out how many elements it should read from the file, simply by reading the first value.

You may want to change your function create to the following:

void create(std::vector<info>& test)
{
    std::ofstream file("info.dat", std::ios::binary );

    for ( const info& inf : test )
    {
        //write "name" member to file
        file.write( reinterpret_cast<const char*>(&inf.name), sizeof inf.name );

        //write "age" member to file
        file.write( reinterpret_cast<const char*>(&inf.age), sizeof inf.age );

        //write "bbl" member to file
        file.write( reinterpret_cast<const char*>(&inf.bbl), sizeof inf.bbl );

        //write "my" member to file, giving it special treatment,
        //because it is a std::vector
        {
            int num_elements = inf.my.size();

            //write number of elements to file
            file.write( reinterpret_cast<const char*>(&num_elements), sizeof num_elements );

            //write the individual elements to file
            for ( int i = 0; i < num_elements; i   )
            {
                file.write( reinterpret_cast<const char*>(&inf.my[i]), sizeof inf.my[i] );
            }
        }
    }

    //verify that stream is still in a good state
    if ( !file )
        throw std::runtime_error( "output error" );
}

Note that I removed std::ios::app in the code above, because it did not seem appropriate for what you are doing.

For reading the file contents, you can now use the following function:

void read(std::vector<info>& test)
{
    std::ifstream file("info.dat", std::ios::binary );

    info inf;

    //try reading new entries from file until end-of-file or error occurs
    for (;;)
    {
        //read "name" member from file
        file.read( reinterpret_cast<char*>(&inf.name), sizeof inf.name );

        //read "age" member from file
        file.read( reinterpret_cast<char*>(&inf.age), sizeof inf.age );

        //read "bbl" member from file
        file.read( reinterpret_cast<char*>(&inf.bbl), sizeof inf.bbl );

        //read "my" member from file, giving it special treatment,
        //because it is a std::vector
        {
            int num_elements;

            //read number of elements from file
            file.read( reinterpret_cast<char*>(&num_elements), sizeof num_elements );

            //don't start loop if loop counter is invalid
            if ( !file )
                break;

            //read the individual elements from file
            for ( int i = 0; i < num_elements; i   )
            {
                MyStruct my;

                file.read( reinterpret_cast<char*>(&my), sizeof my );
                if ( file )
                    inf.my.push_back( my );
                else
                    break;
            }

            //stop main loop if data was not successfully read
            if ( !file )
                break;

            test.push_back( inf );
        }
    }
}

Your entire program will now look like this:

#include <iostream>
#include <fstream>
#include <vector>
#include <cstring>

struct MyStruct
{
    int a;
};

struct info
{
    char name[30];
    int age;
    char bbl[20];
    std::vector<MyStruct> my;
};

void create(std::vector<info>& test)
{
    std::ofstream file("info.dat", std::ios::binary );

    for ( const info& inf : test )
    {
        //write "name" member to file
        file.write( reinterpret_cast<const char*>(&inf.name), sizeof inf.name );

        //write "age" member to file
        file.write( reinterpret_cast<const char*>(&inf.age), sizeof inf.age );

        //write "bbl" member to file
        file.write( reinterpret_cast<const char*>(&inf.bbl), sizeof inf.bbl );

        //write "my" member to file, giving it special treatment,
        //because it is a std::vector
        {
            int num_elements = inf.my.size();

            //write number of elements to file
            file.write( reinterpret_cast<const char*>(&num_elements), sizeof num_elements );

            //write the individual elements to file
            for ( int i = 0; i < num_elements; i   )
            {
                file.write( reinterpret_cast<const char*>(&inf.my[i]), sizeof inf.my[i] );
            }
        }
    }

    //verify that stream is still in a good state
    if ( !file )
        throw std::runtime_error( "output error" );
}

void read(std::vector<info>& test)
{
    std::ifstream file("info.dat", std::ios::binary );

    info inf;

    //try reading new entries from file until end-of-file or error occurs
    for (;;)
    {
        //read "name" member from file
        file.read( reinterpret_cast<char*>(&inf.name), sizeof inf.name );

        //read "age" member from file
        file.read( reinterpret_cast<char*>(&inf.age), sizeof inf.age );

        //read "bbl" member from file
        file.read( reinterpret_cast<char*>(&inf.bbl), sizeof inf.bbl );

        //read "my" member from file, giving it special treatment,
        //because it is a std::vector
        {
            int num_elements;

            //read number of elements from file
            file.read( reinterpret_cast<char*>(&num_elements), sizeof num_elements );

            //don't start loop if loop counter is invalid
            if ( !file )
                break;

            //read the individual elements from file
            for ( int i = 0; i < num_elements; i   )
            {
                MyStruct my;

                file.read( reinterpret_cast<char*>(&my), sizeof my );
                if ( file )
                    inf.my.push_back( my );
                else
                    break;
            }

            //stop main loop if data was not successfully read
            if ( !file )
                break;

            test.push_back( inf );
        }
    }
}

int main()
{
    info info1;
    // create a test vector
    std::vector<info> test;
    info1.age = 3443;
    std::cin >> info1.name;
    std::cin >> info1.bbl;
    MyStruct my;
    my.a = 4;
    info1.my.push_back(my);

    test.push_back(info1);
    
    std::cout << '\n';
    create(test);
    
    test.clear(); // clear the vector

    read( test );

    // print out the contents of test
    for (int i = 0; i < test.size(); i  )
        std::cout << "{ " << test[i].name << ", " << test[i].age << ", " << test[i].bbl << " } ";
    std::cout << '\n';
}

This program has the following output:

TestName
TestBBL

{ TestName, 3443, TestBBL } 

As you can see, the written data was read back properly.

CodePudding user response:

You can read/write vector in binary, or vector of structure, as long as the struct is POD. The whole data can be obtained from vec.data() and the size of data is vec.size() * sizeof(vec[0])

In your example, the structure is not POD, this method can't be used. But the structure can be broken down in to POD sub vector, and we can read/write each individual element.

Note however that this method is OS dependent. The binary data can be stored in big-endian or little-endian format, the structure size may vary between compilers. The file is not portable, at least not without additional work.

(This code requires C 17)

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

struct info_pod
{ char name[30]; int age; char bbl[20]; };

struct info : info_pod
{ std::vector<int> subvec; };

int main()
{
    std::vector<info> src{ 
        { "name1", 100, "bbl1", { {10}, {11}, {12} } },
        { "name2", 101, "bbl2", { {10}, {11}, {12} } },
        { "name3", 102, "bbl3", { {10}, {11}, {12} } }
    };

    std::ofstream fout("info.dat", std::ios::binary); 
    if (!fout)
        return 0;

    for (auto& e : src)
    {
        fout.write((char*)&e, sizeof(info_pod));

        size_t count = e.subvec.size();
        fout.write((char*)&count, sizeof(count));

        auto vecsize = count * sizeof(e.subvec[0]);
        fout.write((char*)e.subvec.data(), vecsize);
    }

    fout.close();

    //read it back
    std::ifstream fin("info.dat", std::ios::binary);
    if (!fin)
        return 0;

    std::vector<info> dst;
    while (true)
    {
        info inf;
        fin.read((char*)&inf, sizeof(info_pod));
        if (fin.gcount() != sizeof(info_pod)) break;

        size_t count;
        fin.read((char*)&count, sizeof(count));
        if (fin.gcount() != sizeof(count)) break;

        inf.subvec.resize(count);
        auto vecsize = count * sizeof(inf.subvec[0]);
        fin.read((char*)inf.subvec.data(), vecsize);
        if (fin.gcount() != vecsize) break;

        dst.push_back(inf);
    }

    for (auto& e : dst)
    {
        std::cout << e.name << ',' << e.age << ',' << e.bbl << " MyStruct: ";
        for (auto& i : e.subvec) std::cout << i << ',';
        std::cout << '\n';
    }
}
  •  Tags:  
  • c
  • Related