使用二进制文件访问违规读取位置

时间:2016-06-22 15:12:31

标签: c++ binaryfiles access-violation reinterpret-cast

首先,我知道有类似问题的帖子,但我找不到其中任何一个的解决方案。

这是针对使用二进制文件和文本文件存储“公司销售数据”的编程作业。 (部门名称,季度和销售额),然后在二进制数据文件中搜索指定的记录并显示它们。

以下是我的代码的重要部分:

#include stuff
...

// Struct to hold division data
struct DIVISION_DATA_S
{
    string divisionName;
    int quarter;
    double sales;
};

int main()
{
    ...

    // Open the data file
    fstream dataFile;
    dataFile.open(dataFilePath, ios::in | ios::out | ios::binary);

    ... Get data from user, store in an instance of my struct ...

    // Dump struct into binary file
    dataFile.write(reinterpret_cast<char *>(&divisionData), sizeof(divisionData));        

    // Cycle through the targets file and display the record from divisiondata.dat for each entry
while(targetsFile >> targetDivisionName)
{       
    int targetQuarter;  // Target quarter
    string targetQuarterStr;
    targetsFile.ignore();   // Ignore the residual '\n' from the ">>" read
    getline(targetsFile, targetQuarterStr);
    targetQuarter = atoi(targetQuarterStr.c_str()); // Parses into an int

    cout << "Target: " << targetDivisionName << " " << targetQuarter << endl;

    // Linear search the data file for the required name and quarter to find sales amount
    double salesOfTarget;
    bool isFound = false;
    while (!isFound && !dataFile.eof())
    {
        cout << "Found division data: " << targetDivisionName << " " << targetQuarter << endl;
        DIVISION_DATA_S divisionData;

        // Read an object from the file, cast as DIVISION_DATA_S
        dataFile.read(reinterpret_cast<char *>(&divisionData), sizeof(divisionData));
        cout << "Successfully read data for " << targetDivisionName << " " << targetQuarter << endl
            << "Name: " << divisionData.divisionName << ", Q: " << divisionData.quarter << ", "
            << "Sales: " << divisionData.sales << endl;

        // Test for a match of both fields
        if (divisionData.divisionName == targetDivisionName && divisionData.quarter == targetQuarter)
        {
            isFound = true;
            cout << "Match!" << endl;
            salesOfTarget = divisionData.sales;
        }
    }
    if (!isFound)   // Error message if record is not found in data file
    {
        cout << "\nError. Could not find record for " << targetDivisionName
            << " division, quarter " << targetQuarter << endl;
    }
    else
    {
        // Display the corresponding record
        cout << "Division: " << targetDivisionName << ", Quarter: " << targetQuarter
            << "Sales: " << salesOfTarget << endl;
        totalSales += salesOfTarget;    // Add current sales to the sales accumulator
        numberOfSalesFound++;   // Increment total number of sales found
    }
}

很抱歉,while循环缺少缩进,复制/粘贴类型搞砸了。

尝试访问从二进制文件读取的信息时出现我的问题。例如,当它尝试执行我为调试添加的cout语句时,它会给我这个错误:

Unhandled exception at 0x0FED70B6 (msvcp140d.dll) in CorporateSalesData.exe: 0xC0000005: Access violation reading location 0x310A0D68.

现在,从我所看到的,似乎这意味着某些东西试图从非常低的内存区域读取,AKA某处某处与空指针有关,但我无法想象这会是怎么回事出现。整个读取操作完全从我的教科书中复制,我不知道reinterpret_chast是什么,更不用说它是如何工作的,或者如何用它修复错误。请帮帮忙?

编辑:感谢您的帮助。为了避免并发症或使用我不完全理解的东西,我将切换到divisionName的c字符串。

3 个答案:

答案 0 :(得分:2)

dataFile.write(reinterpret_cast<char *>(&divisionData), sizeof(divisionData)); 

仅在您拥有POD类型时才有效。当你有一个std::string时,它不起作用。你需要使用以下内容:

// Write the size of the string.
std::string::size_type size = divisionDat.divisionName.size();
dataFile.write(reinterpret_cast<char*>(&size), sizeof(size));

// Now write the string.
dataFile.write(reinterpret_cast<char*>(divisionDat.divisionName.c_str()), size);

// Write the quarter and the sales.
dataFile.write(reinterpret_cast<char*>(&divisionDat.quarter), sizeof(divisionDat.quarter));
dataFile.write(reinterpret_cast<char*>(&divisionDat.sales), sizeof(divisionDat.sales));

更改读取调用以匹配写入调用。

答案 1 :(得分:1)

// Dump struct into binary file
dataFile.write(reinterpret_cast<char *>(&divisionData), sizeof(divisionData)); 

/*...*/

// Read an object from the file, cast as DIVISION_DATA_S
dataFile.read(reinterpret_cast<char *>(&divisionData), sizeof(divisionData));

在任何情况下都不会有效。

std::string使用堆分配的指针来存储它包含的任何字符串数据。您写入文件的内容是字符串的内容,而只是字符串数据所在的地址(以及一些元数据)。如果您随意读取这些指针并将它们视为内存(就像您在cout语句中一样),那么您将引用已删除的内存。

您有两种选择。

如果您想要的只是一个可以轻松序列化的结构,那么只需将其转换为:

// Struct to hold division data
struct DIVISION_DATA_S
{
    char divisionName[500];
    int quarter;
    double sales;
};

当然,使用此样式,您仅限于与名称作为c字符串进行交互,并且也限制为500个字符。

另一个选项是正确序列化此对象。

// Struct to hold division data
struct DIVISION_DATA_S
{
    string divisionName;
    int quarter;
    double sales;

    string serialize() const { //Could also have the signature be std::vector<char>, but this will make writing with it easier.
        string output;
        std::array<char, 8> size_array;
        size_t size_of_string = divisionName.size();
        for(char & c : size_array) {
            c = size_of_string & 0xFF;
            size_of_string >>= 8;
        }
        output.insert(output.end(), size_array.begin(), size_array.end());
        output.insert(output.end(), divisionName.begin(), divisionName.end());
        int temp_quarter = quarter;
        for(char & c : size_array) {
            c = temp_quarter & 0xFF;
            temp_quarter >>= 8;
        }
        output.insert(output.end(), size_array.begin(), size_array.begin() + sizeof(int));
        size_t temp_sales = reinterpret_cast<size_t>(sales);
        for(char & c : size_array) {
            c = temp_sales & 0xFF;
            temp_sales >>= 8;
        }
        output.insert(output.end(), size_array.begin(), size_array.end());
        return output;
    }

    size_t unserialize(const string & input) {
        size_t size_of_string = 0;
        for(int i = 7; i >= 0; i--) {
            size_of_string <<= 8;
            size_of_string += unsigned char(input[i]);
        }
        divisionName = input.substr(7, 7 + size_of_string);
        quarter = 0;
        for(int i = 10 + size_of_string; i >= 7 + size_of_string; i--) {
            quarter <<= 8;
            quarter += unsigned char(input[i]);
        }
        size_t temp_sales = 0;
        for(int i = 18 + size_of_string; i >= 11 + size_of_string; i--) {
            temp_sales <<= 8;
            temp_sales += unsigned char(input[i]);
        }
        sales = reinterpret_cast<double>(temp_sales);
        return 8 + size_of_string + 4 + 8;
    }
};

写文件非常简单:

dataFile << divisionData.serialize();

阅读可能有点困难:

stringstream ss;
ss << dataFile.rdbuf();
string file_data = ss.str();
size_t size = divisionData.unserialize(file_data);
file_data = file_data.substr(size);
size = divisionData.unserialize(file_data);
/*...*/

顺便说一下,我还没有检查我的代码的语法或完整性。此示例旨在作为您需要编写以正确序列化/反序列化复杂对象的代码类型的参考。我相信它是正确的,但我不会把它扔进未经测试的。

答案 2 :(得分:1)

欢迎来到序列化的世界。你正试图搞笑#39;你的结构成文件。这仅适用于非常简单的类型(int,float,char [xxx]),其中数据实际上是内联的。即使它确实有效,您仍然会将数据重新加载到相同类型的机器中(相同的字大小,相同的字节顺序)。

您需要做的是对数据进行序列化,然后将其反序列化。您可以自己创建这样做的方法,也可以在许多标准上使用。有两种基本类型 - 二进制(高效,非人类可读)和文本(效率低但人类可读)

文本

  • JS​​ON
  • YAML
  • XML
  • CSV

二进制

  • protobuf的

boost有一个序列化库http://www.boost.org/doc/libs/1_61_0/libs/serialization/doc/

你也想看看这里

https://isocpp.org/wiki/faq/serialization