Search code examples
c++random-access

C++ Access Violation Error


I wrote a simple program to learn how to use random access filling. It compliles fine but on runtime gives the access violation error. I am only writing and reading a single record.

Header file:

#include<iostream>
#include<string>
#include<fstream>
using namespace std;

#ifndef HEADER_H
#define HEADER_H


class info
{
private:
    int id;
    string name;
public:
    info(int = 0, string = " ");
    void set(int, string);
    void display();
    void write();
    void read();
};

#endif

Implementation file:

#include<iostream>
#include<string>
#include<fstream>
#include"Header.h"
using namespace std;

info::info(int x, string y)
{
    set(x, y);
}

void info::set(int x, string y)
{
    id = x;
    name = y;
}

void info::display()
{
    cout << "\n\n\tid : " << id;
    cout << "\n\tname" << name;
}

void info::write()
{
    ofstream o("info.dat", ios::out | ios::binary | ios::app);
    info a(id, name);
    info *p = &a;
    o.write(reinterpret_cast<const char *>(p), sizeof(info));
    o.close();
    cout << "\n\n\tWrite Successful";
}

void info::read()
{
    ifstream i("info.dat", ios::in | ios::binary);
    i.seekg(0);
    info a(0, "a");
    info *p = &a;
    i.read(reinterpret_cast<char *>(p), sizeof(info));
    i.close();
    p->display();
    cout << "\n\n\tRead Successful";
}

Main:

#include<iostream>
#include<string>
#include<fstream>
#include"Header.h"
using namespace std;

void main()
{
    info a(10, "muaaz");
    a.write();
    a.display();
    info b(2, "m");
    b.read();
}

The error occurs after the read function. The cout "Read Successful" at the end of the read function runs fine and there is no other statement after that in the main. I dont know what is causing the error.


Solution

  • Every occurence of reinterpret_cast in your code should give you a strong hint that something dangerous and error-prone is happening.

    void info::write()
    {
        ofstream o("info.dat", ios::out | ios::binary | ios::app);
        info a(id, name);
        info *p = &a;
        o.write(reinterpret_cast<const char *>(p), sizeof(info));
        o.close();
        cout << "\n\n\tWrite Successful";
    }
    

    This cannot work correctly. All language-lawyer issues aside, consider the implementation point of view here. A std::string normally does not directly contain (as a data member) the text it represents, but instead contains a pointer to dynamically allocated memory where the text is stored.

    So what you do end up doing here is writing a memory address into the file.

    This is already sufficient for the approach to be doomed for any serialisation business, because obviously, memory addresses are different for different runs of the same program.

    But it won't work even in this simple example when the file is written to and read from within the same execution. a is a local object. When write finishes, the object is destroyed, and with it the std::string it contains. When a std::string is destroyed, the memory it had allocated for its contents is released.

    Consequently, as soon as write is done, your file contains an invalid memory address. A few moments later, in the read function, you try to stuff that invalid address into a new std::string. Luckily enough, you get an access violation so that you notice something is wrong. If you were unlucky, the program would have continued to produce the desired behaviour for a while, only to start crashing or doing strange things at a later point.


    Some issues which make the situation even more complicated:

    • The memory layout of std::string is unspecified by the C++ standard. Different compilers with different compiler settings will thus produce different info.dat files even when you have compiled them from exactly the same C++ source code.

    • std::string may or may not use Small-String Optimisation (SSO), a technique which means that for strings with only a few characters, the text is directly stored in the object instead of being dynamically allocated. So you cannot even assume the presence of a pointer.

    • On the other hand, std::string may contain even more pointers to dynamically allocated memory, pointing to the end of the represented text and to the end of the allocated memory (for the capacity member function).


    Considering all of those important internal complexities, it should not surprise you that your attempt at bypassing or ignoring all of them with a simple reinterpret_cast invokes undefined behaviour.

    Serialisation and especially deserialisation of non-POD types is not easy in C++[*] and has not yet been standardised. You may consider a third-party library. As always, Boost is worth a try when it comes to third-party libraries, and as it turns out, it does contain a serialisation library, namely Boost.Serialization.


    [*] Actually, it's not easy in any language, not even in those which pretend to make it easy, such as Java with its highly dangerous Serializable interface.