【问题标题】:Stack Overflow Exception when declaring multidimensional arrays声明多维数组时出现堆栈溢出异常
【发布时间】:2015-09-29 21:19:11
【问题描述】:

我对一般的编程有点陌生,并且在声明 3D 和 4D 数组时遇到了问题。我在 main 函数的开头有几个这样的声明,但我已将问题缩小到这 4 个:

string  reg_perm_mark_name[64][64][64];
short   reg_perm_mark_node_idex[64][64][64];
short   reg_perm_mark_rot[64][64][64][4];
short   reg_perm_mark_trans[64][64][64][3];

当我使用这些运行我的程序时,我的可执行文件中会出现“System.StackOverflowException”。我更喜欢动态分配它们的方式,我现在拥有的方式无论如何都是临时的,我不确定如何正确声明数组指针。

例如,我在 4D 数组 reg_perm_mark_trans 中使用的 4 个元素是 [节点索引][区域索引][标记索引][xyz 坐标]。还有一次总共声明了 35 个多维数组。 (其中大多数是 1D 和 2D)我不确定这是否有帮助。

有人可以告诉我如何使这些 4d 数组工作,或者如何使它们使用指针或向量动态分配吗?请描述一下,我还在学习。

【问题讨论】:

  • 这种“简单”变量的空间故意非常有限。你的数组太大了。使用vector 等(在内部使用new,不受限制。)。 std::vector在网上的使用例子已经够多了。
  • 不过,标准容器不能很好地替代多维数组。
  • @celticminstrel std::array 是裸多维数组的绝佳替代品。当您使用动态大小的多维数组时,std::vector 也是如此。
  • 哦,你说得有道理——std::array 至少是多维数组的不错替代品。不过,std::vector 并没有那么多 - 您必须在手动展平索引或使用“锯齿状数组”而不是真正的多维数组之间做出选择。
  • 请注意我对std::vector 的评论中的动态大小部分。与使用std::vector 相比,您尝试自己管理动态大小 多维数组的开销更大。真正的好处是您可以最轻松地混合和匹配它们,这样您就可以两全其美,而不会因忽略标准容器的有用性而带来所有开销和头痛。

标签: c++ arrays pointers multidimensional-array stack-overflow


【解决方案1】:

为简单起见,假设sizeof(string) == 2(可能更多),您正在尝试在堆栈上分配 (64^3)*9*2 字节。结果是 4,718,592 字节,或大约 4.5 MiB。最有可能的是,您的堆栈中没有 4.5 MiB 可用。

由于这些变量是在main() 中声明的,因此您有两种可能的解决方案:

  1. 声明他们static

  2. main()之外声明它们,作为全局变量。

这将导致它们在程序启动之前被分配,而不是在堆栈上。这两种方法的唯一区别是它们是否会在其他函数中可见。

也可能有一种方法可以告诉您的编译器程序需要更多的堆栈空间,但我认为将它们设为静态可能是这里更好的解决方案。但是,如果它们位于 main() 以外的函数中,您可能需要执行其他操作。

【讨论】:

  • 您的字节计算有问题。它更像是 MB,而不是 GB。那个 9 是从哪里来的?顺便提一句。 std::string 不能使用小于指针(实际上,取决于实现,它要多得多)
  • ...你是对的。这是 MiB,而不是 GiB。而 9 来自他的第四维(4 和 3)加上其他两个是三维的事实的总和。我只是估计sizeof(string) == 2,因为它通过使所有基本类型的大小相同来简化计算。这只是一个估计值。
  • 在很多情况下您不能声明变量static 或使它们成为全局变量(例如多线程),您需要动态分配。
  • 我不会投反对票,因为答案没有错,这反映了努力,我讨厌投反对票,但这确实不是最佳答案。将巨型数组放入静态存储几乎不是惯用的。此外,创建非平凡类型的非函数局部静态变量非常危险。像这样编写的代码在 main 之前调用构造函数。 main 之前的构造函数可能会导致无辜代码出现段错误,具体取决于事物的链接方式,并且在许多样式指南(例如 llvm)中被禁止。
  • string 是一个类,因此它的大小必须远大于 4 个字节,因为它必须存储字符串长度以及分配的内存
【解决方案2】:

一个简单的解决方案是使用静态分配(即将您的数组移到任何函数之外,或将它们标记为static)。

请注意,如果您使用 C++ 数组,则用法和占用空间是相同的,但它们的行为类似于正确的容器:

array<array<array<string,64>,64>,64> reg_perm_mark_name;

要不安全地使用动态分配,你可以这样写:

auto reg_perm_mark_rot = new short[64][64][64][4];
// ...
delete[] reg_perm_mark_rot;

为了安全使用它,从 C++14 开始(注意最里面的维度得到特殊处理):

auto reg_perm_mark_rot = std::make_unique<short[][64][64][4]>(64);

当然,您可以使用 array 来代替具有动态选项的 C 样式数组,但是您将有一个额外的间接级别来使用该数组。

【讨论】:

  • c 数组和 std 数组之间没有额外的间接级别。你应该使用 std::array。
  • 另外,请永远不要告诉人们使用非平凡类型的全局变量,这非常危险,请参阅我的其他评论。
  • @NirFriedman 如果您正在动态分配它。如果您不同意,请出示您的代码。
  • 这些全局字符串一点都不危险,更不用说疯狂了。该标准要求它们在首次使用之前进行初始化(除非另一个单元通过extern 访问它们,我不建议这样做)
  • 这实际上是危险的,因为根据项目的链接结构,它们可能会在退出时被双重破坏。参见例如stackoverflow.com/questions/6714046/…。如果你写一个简单的程序,你会看到这样的变量在 main 之前构造和在之后破坏,这在将代码片段链接在一起时证明不是很健壮。
【解决方案3】:

这一行:

string  reg_perm_mark_name[64][64][64]

在堆栈上声明 64*64*64 = 262144 个字符串。 std::string 通常约为 32 字节,因此约为 8MB。最大堆栈大小通常约为 1MB。

要动态声明数组,您可以使用std::vector。一般来说,多维std::vectors 可能会有点麻烦,通常最好在访问元素时声明一维向量并转换为单个索引:

std::vector<std::string> reg_perm_mark_name(64*64*64);

int i = 13;
int j = 27;
int k = 7;
reg_perm_mark_name[i + 64*j + 64*64*k] = "Hello world!";

但在这种情况下,您可以通过使用 std::array 而不是 std::vector 来非常有效地声明多维 std::vector 内部类型。使用std::array 避免了过多的内存分配,因为它们具有固定大小。我会使用 typedefs 或使用别名来使声明更清晰:

using StrArray = std::array<std::string, 64>;
using StrArray2D = std::array<StrArray, 64>;

std::vector<StrArray2D> reg_perm_mark_name(64);

reg_perm_mark_name[3][4][7] = "Hello world!";

【讨论】:

  • 内部类型是否适合堆栈完全无关紧要。如果外部类型是向量,那么除了一个向量(通常是 3 个单词)之外的所有内容都将在堆上。事实上,如果最外层的类型是向量,而所有的内层类型都是数组,那么您将得到最好的结果:单一分配、几乎没有开销、没有堆栈溢出的可能性以及良好的索引表示法。
  • @NirFriedman 是的,我用错了术语。我只是想说内部类型的大小适合分配块。我会尝试编辑以使其更准确...
  • 最好的答案,希望它超越其他两个,因为他们有严重的问题。
  • 好吧,尽管我有自己的答案,但这个答案值得一票。请注意,我对他的第二个选项有点怀疑。相反,我建议编写一个包装类(尽管即使对于 2D 来说,这也是相当多的工作,对于 3D 和 4D 来说甚至更多。)
  • 为什么第二个选项有问题?
猜你喜欢
  • 2022-01-02
相关资源
最近更新 更多