大数组、std::vector和堆栈溢出

large arrays, std::vector and stack overflow

本文关键字:堆栈 栈溢出 vector std 数组      更新时间:2023-10-16

我有一个从大数组中读取数据的程序,我最初在Visual Studio中将该程序分为两个单独的项目,每个项目都运行良好,但当我试图将它们放在一起时,程序在调试时跳过了一些步骤,表现得很有趣。我对C++很陌生,所以我开始做一些研究,我发现也许我用那些巨大的数组填充了堆栈,我应该试着把它们放在堆上。

我决定为std::vector更改每个数组,并以这种方式初始化它们:

std::vector<double> meanTimeAO = { 0.4437, 0.441, 0.44206, 0.44632, 0.4508, 0.45425,...}

但是,在更改了所有数组之后,当我试图编译编译器时,由于堆栈溢出而崩溃,我认为我通过将数组更改为向量来从堆栈中释放内存空间,但似乎我得到了相反的结果,为什么??

我应该如何处理这些大数组?(它们是固定的,从不改变值或大小)

正如@Ajay的回答和@Cornstrails的评论正确指出的那样,您可以通过在数组上使用staticconstexpr限定符来完全避免堆栈和堆

const static std::array<float, 1000000> a1 = {}; // OK
constexpr    std::array<float, 1000000> a2 = {}; // OK in C++11 onwards

这将数组存储在内存的数据初始化部分(这里有很好的解释)。const仅用于禁止修改a1,不需要它来避免堆栈溢出。声明为constexpr的变量也自动为const,因此不需要限定符。

注意:您也可以通过使数组成为全局变量来实现static的效果,尽管我不建议这样做。

程序堆栈溢出

如果数据是非静态的,那么当元素数量很大时,应该使用std::vector(或其他类型的堆分配内存)。

std::array<float, 1000000> a = {};   // Causes stack-overflow on 32-bit MSVS 2015
std::vector<float> v(1000000);       // OK

这是因为默认堆栈大小约为1MB,100万个浮点需要约4MB。堆的大小受到系统可用内存(RAM)的限制。更多信息请点击此处。

std::vector的缺点是它比std::array慢一点(堆内存分配、释放和访问都比堆栈慢),而且它不是固定大小。但是,您可以将std::vector声明为const,以防止自己(或其他人)意外更改其大小或元素。

const std::vector<float> v = {...}; 

现在,为什么std::vector会导致堆栈溢出还有点神秘。然而,当std::vector在堆上分配其元素时,它也在堆栈上分配指针(32位上为4字节,64位上为8字节)。因此,如果同时有超过250000个std::vector,这也会导致堆栈溢出(在64位系统上约为125000)。

编译器堆栈溢出

编译器和任何程序一样,分配内存——其中一些内存将在堆栈上。MSVC上编译器堆栈溢出的官方错误是致命错误C1063。

考虑到调试器的行为很奇怪,我的建议是尝试通过手动将代码拆分为模块单元并单独编译来隔离有问题的代码。少量的代码可能会消耗大量的堆栈,例如递归地生成大量的函数,从而导致错误。

或者,您的代码可能天生就很复杂,因此它自然需要比堆栈更多的内存。在这种情况下,拆分代码仍然有好处,但您也可以尝试增加MSVC的默认堆栈大小。

改进您的代码

为了改进您的代码,您可以尝试将数据拆分为块。例如,您可以:读取大约256 KB的数组,对其进行处理,将数组写回文件,然后转到下一个256 KB。您可以进一步选择块的大小小于一级缓存的大小(这样就可以同时存储所有块),这将通过最大限度地减少缓存未命中来提高性能。

备注

  1. MSVS 2015(更新2)在编译时产生内部编译器错误

    #include "stdafx.h"
    #include <array>
    int main()
    {
    constexpr std::array<int, 1000000> a = {};
    return 0;
    }
    

    static const变体运行良好,如果我将a移到main之外(使其成为全局变量),那么它也运行良好。

  2. 没有chkstk.asm是不寻常的。矿山位于C:Program Files (x86)Microsoft Visual Studio 14.0VCcrtsrci386chkstk.asm。如果您丢失了它,那么也许可以尝试重新安装MS Visual Studio。

如果数组是固定大小的,并且其元素不变,那么就不需要使用vector。您可以使用std::arrayconst数组或constexpr

constexpr float meanTimeAO[] = { 0.4437f, 0.441f, 0.44206f, 0.44632f, 0.4508f, 0.45425f,...}

指出以上答案中可能忽略的几点:

  1. "程序在调试时跳过了一些步骤,表现得很有趣"。OP,你能提供更多关于你的意思的细节吗?也许,您正在调试一个发布版本,当您遍历代码时,您会发现正在执行的代码行不是您期望下一步执行的代码(这对于具有编译器优化的发布版本来说是完全正确的行为)。

  2. 问题指出编译器因堆栈溢出而崩溃。不是执行的程序。所以问题是编译器的问题。当然,更改代码可能会使编译器不会崩溃,但上面关于在堆栈上分配std::vector的注释与导致编译器崩溃的原因无关。

建议:您可以尝试查看您使用的编译器版本中是否存在任何已知的错误(即,查看您的编译器供应商是否发布了可能解决编译器崩溃问题的最新版本)。

此外,特别是在你的"它们的值或大小从未改变"评论中,试着将数据放入静态常量双数组,而不是std::vectors(甚至链表)。当您可能只应该使用double[]时,一个不变的静态分配的只读链表有点浪费时间。静态const数据是在编译/链接时而不是运行时初始化的,并且存在于自己的内存区域中(严格来说,既不是堆栈也不是堆)。