C++ 中的大数组堆栈溢出

Stack overflow in C++ with big array

好吧,我正在为大学编写一个程序,我必须将数据转储为 HDF 格式。数据转储如下所示:

"1444394028","1","5339","M","873"
"1444394028","1","7045","V","0.34902"
"1444394028","1","7042","M","2"
"1444394028","1","7077","V","0.0470588"
"1444394028","1","5415","M","40"
"1444394028","1","7077","V","0.462745"
"1444394028","1","7076","B","10001101"
"1444394028","1","7074","M","19"
"1444394028","1","7142","M","16"
"1444394028","1","7141","V","0.866667"

对于 HDF5 API 我需要一个数组。所以我现在的方法是,将数据转储写入这样的数组中:

int count = 0;
std::ifstream countInput("share/ObservationDump.txt");
std::string line;

if(!countInput) cout << "Datei nicht gefunden" << endl; 

while( std::getline( countInput, line ) ) {
    count++;
}

cout << count << endl;

struct_t writedata[count];

int i = 0;

std::ifstream dataInput("share/ObservationDump.txt");
std::string line2;

char delimeter(',');

std::string timestampTemp, millisecondsSinceStartTemp, deviceTemp, typeTemp, valueTemp;

while (std::getline(dataInput, timestampTemp, delimeter) ) 
{
    std::getline(dataInput, millisecondsSinceStartTemp, delimeter);
    std::getline(dataInput, deviceTemp, delimeter);
    std::getline(dataInput, typeTemp, delimeter);
    std::getline(dataInput, valueTemp);

    writedata[i].timestamp = atoi(timestampTemp.substr(1, timestampTemp.size()-2).c_str());
    writedata[i].millisecondsSinceStart = atoi(millisecondsSinceStartTemp.substr(1, millisecondsSinceStartTemp.size()-2).c_str());
    writedata[i].device = atoi(deviceTemp.substr(1, deviceTemp.size()-2).c_str());
    writedata[i].value = atof(valueTemp.substr(1, valueTemp.size()-2).c_str());
    writedata[i].type = *(typeTemp.substr(1, typeTemp.size()-2).c_str());

    i++; 
}

struct_t 定义为

struct struct_t
{
    int timestamp;
    int millisecondsSinceStart;
    int device; 
    char type; 
    double value; 
};

正如你们中的一些人可能看到的那样,对于大数据转储(大约 6 万行),数组 writedata 往往会产生堆栈溢出(分段错误)。我需要一个数组来将它传递给我的 HDF 适配器。如何防止溢出?我无法通过广泛的谷歌搜索找到答案。提前致谢!

struct_t writedata[count];

这个数组分配在通常很小的堆栈上,当它到达一个太大的值(这是半任意的)时,这将溢出堆栈。 您最好通过执行以下操作在堆上进行分配:

struct_t* writedata = (struct_t*)malloc(sizeof(struct_t) * count);

然后在完成内存后添加对 free 的相应调用,例如

free(writedata);
writedata = nullptr;

最好在你的 while 循环中检查 i < count,就像你注销数组的末尾一样。坏事可能会发生。

您关注的 example code 是 C 语言,而您编写的代码是 C++。在大多数 情况下,有效的 C 代码就是有效的 C++ 代码,尽管不一定是好的风格;这是不是的时候之一,虽然因为那不是你真正的问题,我会在我的回答结束时留下解释。

当您声明 struct_t writedata[count]; 时,您是在堆栈上创建一个数组。堆栈的大小通常是人为限制的,因此在堆栈上创建一个大数组可能会导致 运行 超出堆栈 space 的问题。这就是你所看到的。典型的解决方案是在堆中创建大型数据结构(尽管堆的主要用途是使数据持续到创建它的函数的 return 之后)。

最 C++ 惯用的访问堆的方法是不直接这样做,而是使用辅助容器 class。在这种情况下,您需要的是一个 std::vector,它可以让您将数据推送到末尾,并会随着您推送更多数据而自动增长。因为它会自动增长,所以你不需要提前指定大小;只需将其声明为 std::vector<struct_t> writedata;(阅读“std::vector of struct_t”)。同样,因为它不需要提前知道大小,所以你也可以忽略整个第一个循环。

向量最初是空的;要将数据放入其中,通常要使用 writedata.push_back()writedata.emplace_back()。其中第一个采用现有的 struct_t;第二个接受你用来创建一个的参数。所有元素都连续存储在内存中,就像在 C 数组中一样,您可以使用 writedata.data().

直接访问它

在函数结束时,当vector超出作用域且不再可访问时,将调用其析构函数并自动清理其使用的内存。


另一种选择,而不是使用 std::vector,是您自己管理内存。 C++ 的做法是使用 newdelete。处理这个问题的最简单方法是仍然像您一样计算 count,但不是通过将数组声明为 count 大小的数组来在堆栈上创建数组,而是 struct_t* writedata = new struct_t[count];.这将在堆中创建一个 count struct_t 的数组,并将 writedata 设置为指向该数组第一个元素的指针。然后你可以像在你的程序中使用数组一样使用它,但是因为它在堆上你不会 运行 出栈 space.

这样做的缺点是需要提前知道大小,需要自己清理自己使用的内存。为此,当您不再需要数据时,您应该 运行 delete[] writedata。之后,writedata 仍将指向内存中的同一个位置,但您的程序不再拥有该数据,因此您需要确保不再使用该值;标准方法是在删除后立即将 writedata 设置为 nullptr.

您还可以使用 newdelete 的 C 等效项,即 mallocfree。它们在您的情况下大部分是等效的,但对于更复杂的示例,您应该记住,这些会使内存未初始化,而 newdelete 将 运行 的 constructors/destructors您创建的内容以确保对象在开始时处于正常状态并且不要在最后留下资源。


现在说明为什么您的原始代码实际上对于任何大小的文件都不是有效的 C++:您的行 struct_t writedata[count]; 试图创建一个 count struct_t 的数组。由于 count 是一个变量,这被称为 可变长度数组 (VLA)。这些事情在较新版本的 C 中是合法的,但在 C++ 中则不然。只要您只想在当前使用的同一系统上编译代码,这一点就值得警告,因为您的编译器似乎支持 VLA 作为扩展。但是,如果你想在任何其他系统上编译你的代码(使其更便携),你不应该使用这样的编译器扩展。