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

Posted

技术标签:

【中文标题】大型数组、std::vector 和堆栈溢出【英文标题】:large arrays, std::vector and stack overflow 【发布时间】:2016-09-22 12:24:00 【问题描述】:

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

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

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

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

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

【问题讨论】:

您是否尝试过只制作数组static const float meanTimeAO[] = 0.4437, 0.441, 0.44206, 0.44632, 0.4508, 0.45425, ... ?如果你那样做,那不应该把它们放在堆栈上。 将带有初始化器的数组放在函数之外 解决所有数组中固定大小的方法是使用称为链表的数据结构,其中每个元素存储一个引用下一个连续元素的对象。或者,您始终可以通过实例化一个新向量并复制所有元素,但这次使用更大的大小来重新定义您的向量。 @MosheRabaev:数组的固定大小不是 OP 的问题;这是一个不变量。在这里使用链表(甚至是std::vector)是不必要的,而且会很迂回。 我喜欢有人在 Stack Overflow 上询问有关堆栈溢出的问题。 【参考方案1】:

如果数组是固定大小的,并且它的元素不变,那么就没有必要使用vector。您可以改用std::arrayconstconstexpr 的数组。

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

【讨论】:

【参考方案2】:

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

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

这会将数组存储在内存的数据初始化部分 (good explanation here)。 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) 的限制。 More on the stack and heap here。

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

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

现在你的std::vectors 导致堆栈溢出的原因有点神秘。然而,虽然std::vector 在堆上分配其元素,但它也在堆栈上分配一个指针(32 位为 4 字节,64 位为 8 字节)。因此,如果您同时在范围内拥有超过 ~250,000 个std::vectors,这也会导致堆栈溢出(或在 64 位系统上约为 125,000 个)。

编译器堆栈溢出

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

鉴于您的调试器行为异常,我的建议是尝试通过手动将代码拆分为模块化单元并单独编译它们来隔离有问题的代码。通过消耗大量堆栈,例如少量代码可能会导致错误。通过递归生成大量函数。

或者,您的代码可能天生就很复杂,以至于它自然需要比堆栈更多的内存。在这种情况下,将代码拆分仍然会有好处,但您也可以尝试increasing the default stack size of MSVC。

改进你的代码

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

注意事项

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

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

    static const 变体可以正常工作,如果我将 a 移到 main 之外(使其成为全局变量),那么它也可以正常工作。

    没有 chkstk.asm 是不寻常的。我的位于 C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\i386\chkstk.asm。如果您错过了它,那么可以尝试重新安装 MS Visual Studio。

【讨论】:

我已经在 VS2015 本身上编译了相同的内容,并且编译得很好。希望你有 RTM。此外,表格本身显示支持constexpr 谢谢@Ajay!我刚刚用 VS2015 中的一个新项目测试了你的代码,它运行良好。在我之前的项目中,我不知道以前出了什么问题。抱歉,我的意思是不支持“扩展的 constexpr”(在“C++ 14 核心语言功能”标题下)-但我认为我混淆了两者,因为我认为需要 C++14 将变量声明为 constexpr。 啊,我找到了错误的根源。当我测试您的代码时,它可以完美运行。但是它只分配 6 个浮点数(忽略 ...)。当我尝试constexpr float a[1000000] = ;constexpr std::array&lt;float, 1000000&gt; a = ; 时,我得到“发生内部编译器错误”,尽管我不知道为什么。对于小尺寸的数组,它可以正常工作。 对不起,我的错 - 我正在初始化我的主函数中的数组。现在我已经将数组移到主函数之外,它可以完美运行。 关于默认堆栈大小:1MB 已经是很多堆栈(Windows,对吗?)。我的系统(Linux、BSD)大约 8 KB。【参考方案3】:

指出上述答案中可能忽略了几件事:

    “程序在调试时跳过一些步骤表现得很有趣”。 OP,你能提供更多关于你的意思的细节吗?也许,您正在调试一个发布版本,并且当您逐步执行代码时,您观察到正在执行的代码行不是您希望接下来执行的代码行(这对于具有编译器优化的发布版本来说是完全正确的行为)。

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

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

另外,特别注意您的“它们的值或大小永远不会改变”评论,尝试将您的数据放入静态 const 双精度数组,而不是 std::vectors(甚至是链表)。当您可能应该只使用 double[] 时,不变的静态分配的只读链表有点浪费时间。 static const 数据在编译/链接时而不是运行时初始化,并且存在于自己的内存区域(严格来说既不是堆栈也不是堆)。

【讨论】:

非常感谢大家的回复......让我澄清我提出的第一点......当我调试我的程序时,如果我一步一步地“进入”它会保持搜索不存在的名为 chkstk.asm 的文件。如果我在那个地方“跳过”程序继续,但我的程序开头的所有初始化变量步骤都被跳过。查看这个站点上的 chkstk.asm 是为什么我得出结论我的堆栈已满有问题,这就是为什么我尝试将更大的数组更改为向量 关于第 2 点,编译器崩溃并出现错误提示 Compiler limit达到堆栈溢出。

以上是关于大型数组、std::vector 和堆栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

当 Fortran 生成大型内部临时数组时,如何避免堆栈溢出?

创建大型数组时,线程“<main>”已溢出其堆栈

保存和堆栈溢出

std::vector 与 std::stack

大数组的堆栈溢出但没有同样大的向量?

Visual Studio C/C++ 数组大小未处理的异常堆栈溢出 [重复]