1

I am new to your forum, so please forgive any missteps. I am working on a c++ project that reads and writes to a binary file. I first tried doing this using full on c++ but when an error popped up, my instructor told me to use c style file manipulation. Low and behold, I get the same error:

Unhandled exception at 0x6087CCC8 (msvcp110d.dll) in CSI_FinalProj_EmployeeDB.exe: 0xC0000005: Access violation reading location 0x00CDDAEC.

This occurs after successfully completing the read and print, and successfully closing the file. It always occurs when the program exits the function and attempts to return to the calling function. If I put it in the main, it blows up after the return, when the program ends.

The function is a simple print function:

void fileClerkType::printRecord(int id)const
{

    FILE* spRead;
    employeeType record;
    long location;
    long size;

    location = id - 1;                  
    size = sizeof(employeeType);

    spRead = fopen("companyFile.dat", "r");

    fseek(spRead, location*size, SEEK_SET);
    fread(&record, sizeof(employeeType), 1, spRead);

    // If a record has been deleted, the id will be 0
    // In that case, don't print
    if (record.getEmployeeID() != 0)
    {

        cout << record << endl;
        fread(&record, sizeof(employeeType), 1, spRead);
    }

    fclose(spRead);

}//Unhandled exception at 0x5065CCC8 (msvcp110d.dll) in
//CSI_FinalProj_EmployeeDB.exe: 0xC0000005: Access violation
//reading location 0x00CDDAEC.

As I said, the function works perfectly. employeeType is a class that has:

2 ints, three strings, and a float

Here is the original c++ version with the same problem. The only difference is that this prints all of the records. It also works perfectly.:

void administratorType::showAllRecords()
{

    long test;
    long position = 0;
    long recordSize = sizeof(employeeType);

    ifstream inFile("EmployeesNew.dat", ios::in | ios::binary);
    employeeType buffer; // empty employeeType

    if(inFile.is_open())
    {

        inFile.seekg((position * recordSize), ios::beg);
        test = inFile.peek(); // Debug
        inFile.read(reinterpret_cast<char*>(&buffer), recordSize);

        position = 0;

        while(position < getRecordCount())
        {

            inFile.seekg((position * recordSize), ios::beg);
            test = inFile.peek();
            inFile.read(reinterpret_cast<char*>(&buffer), recordSize);
            outputRecord(cout, buffer);
            position++;

        }

        inFile.close();

    }

}// Runs fine to here, but throws error when leaving the function
// Unhandled exception at 0x5408CCC8 (msvcp110d.dll) in
// ProjectName.exe: 0xC0000005: Access violation
// reading location 0x0137D3B4.

It has to be an implementation issue. But I cannot see it. Is there something in the implementation that is causing the pointers keeping track of function calls and returns to be corrupted? Thank you in advance for your help.

Sorry, here is the list of member variables for the Employee class. They are not fixed length strings:

int age;
int employeeID; // Auto-generated
float salary;
string lastName;
string firstName;
string ssn;
Steve Smith
  • 11
  • 1
  • 3
  • 1
    Show a **complete** minimal example, you haven't even provided the definition of `employeeType`, it might not be packed, it might have non-trivially copyable members. `three strings` if you mean `std::string`, you're going to have a bad time. – user657267 Nov 20 '15 at 01:03
  • Have you run your program in a debugger to get more precise insight on where and what is causing the crash? – kaylum Nov 20 '15 at 01:06
  • "three strings". This is a bit suspicious. Are the strings always fixed length? You really do need to show the exact definition of `employeeType`. – kaylum Nov 20 '15 at 01:09
  • I added a list of variables for the Employee class to this post. I will change the strings to char arrays to see if that fixes it. And will let you know. – Steve Smith Nov 20 '15 at 01:23
  • we guess that you are writing out class instances by doing write(&object, sizeof(object)), You simply cannot do that. You have to serialize the data out and deserialize it back. google c++ serialization. Personally I would serialize to json – pm100 Nov 20 '15 at 01:24
  • If I serialize the data, will I be able to keep the strings? – Steve Smith Nov 20 '15 at 01:29
  • You aren't keeping the strings now. A std::string contains a pointer to heap allocated data. You are writing out the pointer and not doing anything to save the actual heap allocated data. Then when you read it in you set the pointer to where the data used to be on the heap - but of course it isn't there and you have corrupted memory. You need to make a function that saves and restores the data member by member (in C++ you would use operator<< and operator>> to do that) – Jerry Jeremiah Nov 20 '15 at 01:44
  • Not sure if I understand, you will have to serialize the strings into the file and deserialize back. You can keep using std::string in your classes (and you should - its a Good Thing(tm)). People are saying that if you had raw fixed size char arrays in your classes you could blindly serialize (assuming you read back on the same type of machine). I would not do that, I would properly serizlie – pm100 Nov 20 '15 at 01:45
  • Problem solved. You guys all rock. Taking the advice of all of you, I opted for a struct which contains all of the member variables that were in the class, and converted all of the strings to char arrays and then worked through the c++ version of file manipulation to come up with the solution. With all of that, I now have code that successfully randomly accesses the file for both read and write. I will add a working version of a test program to my original question. How do I give you guys credit for an answer? – Steve Smith Nov 23 '15 at 15:47

2 Answers2

0

std::string is not a trivially copyable type, so no class that has one as a member is trivially copyable either.

You cannot read or write to non-trivially copyable types bytewise like this. The function might not crash when you read from the string due to most libraries having adopted SSO (assuming lastName, firstName, and ssn are short enough), but you'll still run into issues during destruction.

The canonical way to serialize data in c++ is to overload the stream operator, here's an example:

std::istream& operator>>(std::istream& stream, employeeType& employee)
{
  return stream >>
    employee.age        >>
    employee.employeeID >>
    employee.salary     >>
    employee.lastName   >>
    employee.firstName  >>
    employee.ssn;
}

std::ostream& operator<<(std::ostream& stream, employeeType const& employee)
{
  return stream <<
    employee.age        << ' ' <<
    employee.employeeID << ' ' <<
    employee.salary     << ' ' <<
    employee.lastName   << ' ' <<
    employee.firstName  << ' ' <<
    employee.ssn        << '\n';
}

Records can be read or written in a loop with something like

for (employeeType e; inFile >> e;)
  //do something with e

Or you can even copy them into a vector with

std::vector<employeeType> employees(
  std::istream_iterator<employeeType>(inFile),
  std::istream_iterator<employeeType>()
);
Community
  • 1
  • 1
user657267
  • 20,568
  • 5
  • 58
  • 77
  • Thanks user657267. I already have the stream operator overloads in place. And the for loop may work, but I have to be able to access the records randomly. When I was using the stream operators, I could access the files, but as soon as I started trying the random access using read and changing the pointers, everything went wrong. – Steve Smith Nov 20 '15 at 02:04
  • if you are going to random access things then they need to be of a fixed size - each row in the file must be , say , 500 bytes so you can cacluate its start offset. Or use sqlite (v easy to use) – pm100 Nov 20 '15 at 02:07
  • @SteveSmith Random access should be pretty trivial to implement given that each record is a single line. Unless the file is gargantuan I'd just slurp it into a vector as above, instant random access with `employees[40]` or whatever. – user657267 Nov 20 '15 at 02:09
  • Thanks again @user657267, but I am not allowed to pull the entire file into memory. Basically the only information I am allowed to store locally are the sorting field and the employeeID, so if I need to sort by name, I would have to access each name and ID (which i could do sequentially),put it into a linked list then sort the list. That is where the joy ends because I will have to attack randomly from that point. It is looking like the char arrays will be my best bet. It's definitely not desired. – Steve Smith Nov 20 '15 at 02:47
  • @SteveSmith Sounds like a ridiculous requirement, but it's easily solvable by seeking to the top of the file before each read and then using `getline` or `ignore` in a loop to ignore `ID - 1` lines. – user657267 Nov 20 '15 at 02:50
  • @user657267 Please don't get me started on the requirements. Is there any preprocessor directives I can use to bypass that warning just for this project without causing catastrophic failures? Like you said, it is a small db. Only 20 records or so. – Steve Smith Nov 20 '15 at 03:01
  • @SteveSmith Bypass what warning? You mean the crash? Absolutely not, you're invoking undefined behavior by attempting to trivially copy something that can't be copied. Seriously just do `for (int i = 0; i < lineToSeek; ++i) inFile.ignore(std::numeric_limits::max(), '\n');`. – user657267 Nov 20 '15 at 04:03
0

I created a struct to hold the data being read to the file, then converted all strings to char arrays. Doing each of those did not work, but the combination did. The following is the test program with a main() and a test class (with a struct. This is what I used to find the solution. This is a working program for those of you seeking a way to read/write binary files randomly (unless I screwed it up while formatting it in here).

#include <iostream>
#include <fstream>
#include <string>
#include <iomanip>

using namespace std;

struct STUDENT
{

    char lName[21];
    int id;
    float sal;

}; 

class Person
{
public:

    struct STUDENT student;

    string getlName() const
    {

        return student.lName;

    }

    int getID() const
    {

        return student.id;

    }
    float getSal() const
    {

        return student.sal;

    }

    // Insertion operator
    friend std::ostream& operator<<(std::ostream& os, const Person& p)
    {

        // write out individual members of the struct with
        // an end of line between each one
        os << p.student.id << ' ' << p.student.lName
            << ' ' << p.student.sal << '\n';

        return os;

    }

    // Extraction operator
    friend std::istream& operator>>(std::istream& is, Person& p)
    {

        // read in individual members of struct
        is >> p.student.id >> p.student.lName >> p.student.sal;

        return is;
    }

    Person()
    {

    }

};


void outputLine( ostream&, const STUDENT&);


int main()
{

    char lName[21] = {}; // Extra char for null
    int id;
    float sal;
    int size = sizeof(STUDENT);
    string more;
    bool exit_now = false;

    STUDENT buffer;
    Person person;

    // In order to randomly access data without destroying the file,
    //  you must use in and out (read/write mode).
    fstream outFile("testFile.dat", ios::in | ios::out | ios::binary);

    // Ensure file is opened
    if(!outFile)
    {

        cerr << "Error: Out File could not be opened" << endl;
        exit(1);

    }

    // ************* Random access inserting *************
    do
    {

        cout << "Enter last Name\n?";

        cin.getline(lName, 21);
        int test;
        test = strlen(lName); // FYI: this works to get char count 
        cout << "Enter salary\n?";
        cin >> sal;

        cout << "Enter ID\n?";
        cin >> id;

        strcpy_s(person.student.lName, lName); // copy input to struct
        person.student.sal = sal;
        person.student.id = id;
        cout << person; // object being printed

        outFile.seekp((person.student.id - 1) * size);
        outFile.write(reinterpret_cast<const char* >(&person.student), size);

        // Need this to get the next name
        cin.clear();
        cin.ignore();

        cout << "Do you want to add another record? (yes or no)\n?"
             << endl;
        cin >> more;

        if (more == "no")
            exit_now = true;

        // Need this to get the next name properly
        cin.clear();
        cin.ignore();


    }while(exit_now == false);

    outFile.close();


    // ************* Display Data *************

    fstream inFile("testFile.dat", ios::in);

    if(inFile) // Is there a connection
    {

        int target = 0;
        int index = 0;
        int position;

        cout << "All records:" << endl;

        while(inFile)
        {

            inFile.read(reinterpret_cast<char*>(&buffer), size);
            if (buffer.id > 0)
            {

                target = inFile.tellg(); // Debug
                cout << buffer.lName << endl;

            }

            //cout << buffer << endl; // This works
            //cout << buffer.id << endl; // This works

        }

        cout << endl << "Search for a record by id" << endl << endl;
        cout << "Enter an id: (0 to exit)" << endl;

        cin >> target;

        while(target > 0)
        {

            index = target - 1;

            inFile.clear(); // Clear the flags. If the fail flags are
                            // are set, seekg() will not work.


            // Position the file pointer
            inFile.seekg(sizeof(Person)*index, ios::beg);

            // Read information into the buffer (Person object)
            //  starting at the file pointer
            inFile.read(reinterpret_cast<char*>(&buffer), size);

            cout << buffer.lName << endl;
            outputLine(cout, buffer);

            cout << "Enter an id: (0 to exit)" << endl;
            cin.clear();
            cin >> target;

        }

        inFile.close();
        cin.clear();
        cin.get();

    }else
        cerr << endl << "Error: Could not complet the file connection."
             << "\nData could not be read."<< endl;

    return 0;

}

void outputLine( ostream& output, const STUDENT& record)
{

    //output << record << endl; // This works also

    output << left << setw(20) << record.lName
            << setw(5) << record.id << setprecision(2)
            << right << fixed << showpoint
            << record.sal << endl;

}
Steve Smith
  • 11
  • 1
  • 3