CodeBrewSlam
CodeBrewSlam

Reputation: 23

Program closes before function returns to main. Exception thrown: read access violation. **_Val** was 0x19B6C345688

My displayRecord() function runs correctly. It will display the given record, then pause for a second, but then the program closes before the function returns to main().

After debugging, I received this error:

Exception thrown: read access violation.
_Val was 0x19B6C345688.

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

struct Item
{
    string description;
    int quantity;
    double wCost;
    double rCost;
    string dateAdded;
};

int addRecord();
int displayRecord();
int changeRecord();

int main()
{  
    cout << "Inventory Program" << endl;
    cout << "-------------------------------" << endl << endl;

    char again = 'y';
    int selection = 0;

    do 
    {
        cout << "MENU" << endl;
        cout << "----" << endl;
        cout << "1. Add a record" << endl;
        cout << "2. Display a record" << endl;
        cout << "3. Change a record" << endl;
        cout << "4. Exit Program" << endl << endl;

        do
        {
            cout << "Please enter a selection from the menu (1, 2, 3, or 4): ";
            cin >> selection;

            if (selection != 1 && selection != 2 && selection != 3 && selection != 4)
            {
                cout << "\nYou must enter a valid selection of either 1, 2, 3, or 4. Please try again.\n\n";
                
            }

        } while (selection != 1 && selection != 2 && selection != 3 && selection != 4);

        if (selection == 4)
        {
            cout << "Exiting Program..." << endl;
            cin.clear();
            return 0;
        }

        if (selection == 1)
        {   
            if (addRecord() == 1)
            {
                return 1;
            }
        }

        else if (selection == 2)
        {
            if (displayRecord() == 1)
            {
                return 1;
            }
        }

        else
        {
            if (changeRecord() == 1)
            {
                return 1;
            }
        }

        cout << "Return to menu? (Type Y for yes): ";
        cin >> again;

    } while (again == 'y' || again == 'Y');
    

    cout << "-------------------------------" << endl << endl;

    system("pause");

    return 0;

}

int displayRecord()
{
    fstream inventoryFile("inventory.dat", ios::in | ios::binary);
    if (!inventoryFile)
    {
        cout << "ERROR: Cannot open file. Aborting Program." << endl;
        return 1;
    }

    Item item;
    long recNum = 0;
    
    cout << "\nEnter the record number you wish to display: ";
    cin >> recNum;

    inventoryFile.seekg((recNum - 1) * sizeof(item), ios::beg);
    inventoryFile.read(reinterpret_cast<char*>(&item), sizeof(item));
    
    if (!inventoryFile)
    {
        cout << "\nERROR: Record does not exist." << endl << endl;
        inventoryFile.close();
        return 0;
    }

    else
    {
        cout << endl;
        cout << showpoint << setprecision(2) << fixed << endl;
        cout << "Item Description: " << item.description << endl;
        cout << "Quantity        : " << item.quantity << endl;
        cout << "Wholesale Cost  : " << item.wCost << endl;
        cout << "Retail Cost     : " << item.rCost << endl;
        cout << "Date Added:     : " << item.dateAdded << endl << endl;
        inventoryFile.clear();
    }
                  
    inventoryFile.close();

    return 0;
}

I have another branch where I open the file in main() and pass the file to the function, but the same thing happens.

I have used clear() and seekg() to set the position at the beginning of the file.

Upvotes: 2

Views: 65

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597896

Your Item type is non-trivial, as it contains non-trivial std::string members. As such, you cannot use istream::read() and ostream::write() to read/write Item objects in binary mode the way you are doing.

std::string contains a pointer to its character data, which may be (and usually is) located elsewhere in memory, outside of the std::string object. You are reading/writing the internal state of the std::string objects, not their character data. So, when you write a file, then restart the program, and then read the file, you will not end up with valid string objects.

To read/write your items in binary mode, they need to be of a trivial type. And to facilitate the random seeking that your code is using, those items need to be a fixed size. Which means, you will have to use fixed-length char[] arrays for your strings, eg:

struct Item
{
    char description[256];
    int quantity;
    double wCost;
    double rCost;
    char dateAdded[32];
};

Otherwise, if you want to read/write std::string values then you must (de-)serialize the Item objects instead (which is not kind to random seeking), eg:

#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>
#include <cstdint>
#include <cstdio>
using namespace std;

struct Item
{
    string description;
    int quantity;
    double wCost;
    double rCost;
    string dateAdded;
};

template<typename T>
istream& read(istream &in, T &value)
{
    return in.read(reinterpret_cast<char*>(&value), sizeof(value));
}

template<typename T>
ostream& write(ostream &out, const T &value)
{
    return out.write(reinterpret_cast<const char*>(&value), sizeof(value));
}

istream& read(istream &in, string &value)
{
    uint32_t size;
    if (in.read(reinterpret_cast<char*>(&size), sizeof(size)))
    {
        value.resize(size);
        in.read(value.data(), size);
    }
    return in;
}

ostream& write(ostream &out, const string &value)
{
    uint32_t size = value.size();
    if (out.write(reinterpret_cast<char*>(&size), sizeof(size)))
        out.write(value.c_str(), size);
    return out;
}

istream& operator>>(istream &in, Item &item)
{
    read(in, item.description);
    read(in, item.quantity);
    read(in, item.wCost);
    read(in, item.rCost);
    read(in, item.dateAdded);
    return in;
}

ostream& operator<<(ostream &out, const Item &item)
{
    write(out, item.description);
    write(out, item.quantity);
    write(out, item.wCost);
    write(out, item.rCost);
    write(out, item.dateAdded);
    return out;
}

...

int addRecord()
{
    Item item;
    ...

    ofstream inventoryFile("inventory.dat", ios::binary | ios::app);
    if (!inventoryFile)
    {
        cout << "ERROR: Cannot open file. Aborting Program." << endl;
        return 1;
    }

    if (!(inventoryFile << item))
    {
        cout << "ERROR: Cannot write to file. Aborting Program." << endl;
        return 1;
    }

    return 0;
}

int displayRecord()
{
    ifstream inventoryFile("inventory.dat", ios::binary);
    if (!inventoryFile)
    {
        cout << "ERROR: Cannot open file. Aborting Program." << endl;
        return 1;
    }

    long recNum = 0;
    
    cout << "\nEnter the record number you wish to display: ";
    cin >> recNum;

    Item item;
    while (recNum > 1)
    {
        if (!(inventoryFile >> item))
        {
            cout << "\nERROR: Record does not exist." << endl << endl;
            return 0;
        }
        ---recNum;
    }
    
    if (!(inventoryFile >> item))
    {
        cout << "ERROR: Cannot read from file. Aborting Program." << endl;
        return 1;
    }

    cout << endl;
    cout << showpoint << setprecision(2) << fixed << endl;
    cout << "Item Description: " << item.description << endl;
    cout << "Quantity        : " << item.quantity << endl;
    cout << "Wholesale Cost  : " << item.wCost << endl;
    cout << "Retail Cost     : " << item.rCost << endl;
    cout << "Date Added:     : " << item.dateAdded << endl << endl;

    return 0;
}

int changeRecord()
{
    ifstream inventoryFile("inventory.dat", ios::binary);
    if (!inventoryFile)
    {
        cout << "ERROR: Cannot open file. Aborting Program." << endl;
        return 1;
    }

    ofstream inventoryTemp("inventory.tmp", ios::binary);
    if (!inventoryTemp)
    {
        cout << "ERROR: Cannot create file. Aborting Program." << endl;
        return 1;
    }

    long recNum = 0;
    
    cout << "\nEnter the record number you wish to change: ";
    cin >> recNum;

    Item item;
    while (recNum > 1)
    {
        if (!(inventoryFile >> item))
        {
            cout << "\nERROR: Record does not exist." << endl << endl;
            inventoryTemp.close();
            remove("inventory.tmp");
            return 0;
        }
        if (!(inventoryTemp << item))
        {
            cout << "ERROR: Cannot write to file. Aborting Program." << endl;
            inventoryTemp.close();
            remove("inventory.tmp");
            return 1;
        }
        ---recNum;
    }
    
    if (!(inventoryFile >> item))
    {
        cout << "ERROR: Cannot read from file. Aborting Program." << endl;
        inventoryTemp.close();
        remove("inventory.tmp");
        return 1;
    }

    // display item...
    // ask user for new values...

    if (!(inventoryTemp << item))
    {
        cout << "ERROR: Cannot write to file. Aborting Program." << endl;
        inventoryTemp.close();
        remove("inventory.tmp");
        return 1;
    };

    while (inventoryFile >> item)
    {
        if (!(inventoryTemp << item))
        {
            cout << "ERROR: Cannot write to file. Aborting Program." << endl;
            inventoryTemp.close();
            remove("inventory.tmp");
            return 1;
        }
    }

    if (!inventoryFile.eof())
    {
        cout << "ERROR: Cannot read from file. Aborting Program." << endl;
        inventoryTemp.close();
        remove("inventory.tmp");
        return 1;
    }

    inventoryFile.close();
    inventoryTemp.close();

    remove("inventory.bak");
    rename("inventory.dat", "inventory.bak");
    rename("inventory.tmp", "inventory.dat");

    return 0;
}

Upvotes: 4

Related Questions