Search code examples
c++fileoopfile-writingfile-read

File I/O Binary Dynamic Array Crashed


#include iostream
#include cmath
#include fstream
#include cstdlib
#include string
using namespace std;
class Device {//Input and store Device Description and Serial Numbers
protected:
    string  serial_number;
    string device_description;
public:
    Device() {
        serial_number = ("6DCMQ32");
        device_description = ("TheDell");
    }
};
class Test {//Input and store Test Description, recent day, and month; 
Calculate the next day
protected:
    string Test_Description;
    static int recent_month, recent_day, recent_year, new_month;
    static int nmonth, next_month, next_day, next_year, max_day;
public:
    Test() {
        Test_Description = ("Virtual");
    }
    static void getMonth(ostream & out) {//Calculates the next/new month
        next_month = recent_month + nmonth;
        new_month = next_month % 12;
        if (next_month >= 12) {
            cout << "The next Date: " << new_month << " / ";
        }
        else {
            out << "The next Date: " << next_month << " / ";
        }
    }
    static void getDay(ostream & out) { //Calculates day of next month
        if (new_month == 4 || new_month == 6 || new_month == 9 || new_month == 11) {
            max_day = 30;
        }
        else if (new_month == 2) {
            max_day = 29;
        }
        else {
            max_day = 31;
        }
        if (recent_day > max_day) {
            out << max_day << " / ";
        }
        else {
            out << recent_day << " / ";
        }
    }
    static void getYear(ostream & out) {// Calculate the year of next month
        next_year = recent_year + next_month;
        if (next_year >= 12) {
            out << recent_year + (next_month / 12) << endl;
        }
        else {
           out << next_year << endl;
        }
    }
    static void getDate(ostream & out) {// Collects the output of each element of next date
        getMonth(out), getDay(out), getYear(out);
    }
};
int Test::recent_month;
int Test::recent_day;
int Test::recent_year;
int Test::new_month;
int Test::nmonth;
int Test::next_month;
int Test::next_day;
int Test::next_year;
int Test::max_day;
class Lab : public Device, public Test {
protected:
    static int n;
public:
    friend istream & operator>>(istream & in, Lab & lab) {// Inputs 
        cout << "Enter Device Desciption and Serial Number: ";
        getline(cin, lab.device_description);
        getline(cin, lab.serial_number);
        cout << "Enter Test Desciption: ";
        getline(cin, lab.Test_Description);
        cout << "Enter the Number of months: ";
        in >> nmonth;
        cout << "Enter the Most Recent Date(mm/dd/yyyy): ";
        in >> recent_month >> recent_day >> recent_year;
        return in;
    }
    friend ostream & operator<<(ostream & out, Lab & lab) {//Outputs everything in Device Class
        out << lab.device_description << endl;
        out << lab.serial_number << endl;
        out << lab.Test_Description << endl;
        getDate(out);
        return out;
    }
    static void getN() {
        cout << "Enter the number of devices: ";
        cin >> n;
    }
    static void getWrite() {
        Lab *obj = new Lab[n];
        if (obj == 0) {
            cout << "Memory Error";
            exit(1);
        }
        for (int i = 0; i<n; i++) {
            cin >> obj[i];
            cout << endl;
        }
        ofstream myfile("Device.dat", ios::binary);
        myfile.write((char*) obj, n * sizeof(Lab));
        delete[] obj;
    }
    static void getRead() {
        ifstream file2("Device.dat", ios::binary);
        Lab *obj2 = new Lab[n];
        if (obj2 == 0) {
            cout << "Memory Error";
            exit(1);
        }
        file2.read((char*) obj2, n * sizeof(Lab));
        for (int i = 0; i < n; i++) {
            cout << obj2[i];
            cout << endl;
        }
        delete[] obj2;
    }
};
int Lab::n;
void main() {
    Lab L;
    L.getN();
    L.getWrite();
    L.getRead();
    getchar();
    getchar();
    system("pause");
}

The program keeps on crashing after outputting the values

Purpose: is to enter the number of months for the next test date of device with input of serial number, Device Description, Test Description, recent date, and the number of months of two tests. At the end the program must be searched by having the user to input the serial number and the next date, if these two are valid everything in the device is listed out.

I am using Microsoft Visual Studios 2017


Solution

  • std::string is far too complicated a data structure to simply write to a file unfortunately. At it's simplest, a string is a pointer to a character array and an integer storing the length of the array. When you write a pointer to a file, you write the address, not the data at the address. When you read the string back latter, odds are very good you wind up with a stale address pointing to memory that the program does not own and much crashing badness. Worse is if the read-back address points to something that does exist in the program. These generally don't crash right away and lead you away from the actual bug because the corruptor is sitting in another bit of code entirely whistling smugly as you blame and debug the wrong code. Either way accessing memory that has not been assigned to the pointer invokes Undefined Behaviour, and with UB anything can happen. Don't count on crashes or consistency.

    Generally you would use your operator << overloads to serialize the structure to the file rather than trying to binary write. If you have to do binary writes, you need to create a protocol that describes how data must be written and read.

    The protocol is going to be a set of functions that convert simpler data types to and from their file equivalents.

    A typical method for writing a string is to first write the length of the string and then write the contents of the string. Something like

    uint32 len = str.length(); //fixed width length
    len = htonl(len); // fixed endian
    myfile.write((char*) &len, sizeof(len)); //write length
    myfile.write(str.data(), str.length()); //write string
    

    And reading

    uint32 len; //fixed width length
    myfile.read((char*) &len, sizeof(len)); //read length
    len = ntohl(len); // unfix endian
    std::string str(len, ' '); //string and allocate size
    myfile.write(str.data(), len); //read string C++17 or
    //myfile.write(&str[0], len); //read string before C++17
    

    bundle these up inside functions and you've started on your protocol. Add to them functions for other data types you need to store.

    These functions are then called by functions that convert larger datatypes and on up until you reach the most complex of the structures you need to write. For an array, use a loop. If you have a length of variable size, prefix the length just like with a string.

    Side note: When reading or writing numbers care must be taken to ensure the number is a known, fixed size. int, for example, can be any size 16 bits or greater so long at it's not larger thanlong. You don't necessarily know that the file reader will be using the same sizedint`, so you should prefer a Fixed Width Integer large enough to store the values required. Different computers also may store their binary information in different orders. This is called Byte Order or Endian. Make sure everyone is using the same endian.