意外的运行时错误(分段错误)

时间:2017-03-23 11:48:13

标签: c++ new-operator binaryfiles dynamic-memory-allocation delete-operator

我目前正在优化内存密集型应用程序以占用更少的内存。我在以下代码中尝试做的是动态分配文件流对象ifstreamofstream,以便在不再需要它之后准确地释放它们。该代码完美地用于ofstream的分配/解除分配,但在运行时崩溃,因为当ifstream的内存内容被解除分配时可能存在分段错误。以下是原始代码的片段:

#include <fstream>
using namespace std;

// Dummy class to emulate the issue at hand
class dummy {
    private:
    int randINT;
    static bool isSeeded; 
    public:
    dummy() { randINT=rand(); }
    int getVal() { return randINT; }
};
bool dummy::isSeeded=false;

int main(int argc, const char* argv[]) {
    // Binary file I/O starts here
    dummy * obj;
    ofstream * outputFile;
    ifstream * inputFile;
    outputFile=new ofstream("bFile.bin",ios::binary);
    if (!(*outputFile).fail()) {
        obj=new dummy;
        cout << "Value to be stored: " << (*obj).getVal() << "\n";
        (*outputFile).write((char *) obj, sizeof(*obj)); // Save object to file
        (*outputFile).close();
        delete obj;
        // don't assign NULL to obj; obj MUST retain the address of the previous object it pointed to
    } else {
        cout << "Error in opening bFile.bin for writing data.\n";
        exit(1);
    }
    delete outputFile; // This line throws no errors!
    inputFile=new ifstream("bFile.bin",ios::binary);
    if (!(*inputFile).fail()) {
        (*inputFile).read((char *) obj,sizeof(dummy)); // Read the object of type 'dummy' from the binary file and allocate the object at the address pointed by 'obj' i.e. the address of the previously de-allocated object of type 'dummy' 
        cout << "Stored Value: " << (*obj).getVal() << "\n";
        (*inputFile).close();
    } else {
        cout << "Error in opening bFile.bin for reading data.\n";
        exit(1);
    }
    delete inputFile; // Runtime error is thrown here for no reason!

    cout << "\n-----END OF PROGRAM-----\n";

}

当用户唤起保存功能时,上面的代码将对象保存到二进制文件。如上面的代码所述,inputFile指针的解除分配会引发运行时错误。

请注意我使用clang(更具体地说,是clang ++)来编译项目。

提前致谢。

1 个答案:

答案 0 :(得分:2)

std::basic_istream::read(char_type*address, streamsize count)最多可读取count个字符,并将其放在address。它似乎相信,address创建任何对象。 address必须指向一个有效的内存块,其大小至少为count*sizeof(char_type)。通过传递delete d对象的地址,您违反了该条件:您的代码已损坏且分段错误并非意外

修改

你正在做的是undefined behaviour,简短的UB。当你这样做时,没有任何保证,任何关于什么发生的顺序无效的逻辑。该程序可以执行任何操作,包括立即崩溃,运行一段时间然后崩溃,或"make demons fly out of your nose"

在您的情况下,我怀疑std::basic_istream::read()写入未受保护的内存会导致某些数据被覆盖,例如另一个对象的地址,这会导致分段错误。但这是纯粹的推测,并不值得追求。

在您的情况下,不会创建任何对象。二进制文件只包含一些字节。 read()将它们复制到提供的地址,但不是为此目的而保留的。要避免使用UB,只需添加

即可
obj = new dummy;

read之前创建一个对象。

如果要重新使用上一个对象的内存,可以使用placement new(请参阅该链接的第9和第10点)。例如

char*buffer = nullptr;                  // pointer to potential memory buffer
if(writing) {
  if(!buffer)
    buffer = new char[sizeof(dummy)];   // reserve memory buffer
  auto pobj = new(buffer) dummy(args);  // create object in buffer
  write(buffer,sizeof(dummy));          // write bytes of object
  pobj->~pobj();                        // destruct object, but don't free buffer
}
if(reading) {
  if(!buffer)
    buffer = new char[sizeof(dummy)];   // not required if writing earlier
  read(buffer,sizeof(dummy));           // read bytes into object
  auto pobj = reinterpret_case<dummy*>(buffer); // no guarantees here
  use(pobj);                            // do something with the object read
  pobj->~pobj();                        // destruct object
}
delete[] buffer;                        // free reserved memory

请注意,如果读取不生成有效对象,则该对象的后续使用(即对其析构函数的调用)可能会崩溃。

然而,所有这些微观优化无论如何都是毫无意义的(如果你可以避免很多newdelete的调用,那么这是值得做的。不要浪费你的时间。