用更少的内存在 C++ 中实现二维数组
Posted
技术标签:
【中文标题】用更少的内存在 C++ 中实现二维数组【英文标题】:Implement 2d array in C++ with less memory 【发布时间】:2015-04-17 20:31:46 【问题描述】:我需要一个二维数组,它是类的一个字段。 x 是宽度,y 是高度。
我写过这样的代码:
#include <iostream>
int main()
char ** tab;
int x, y;
std::cin >> x >> y;
tab = new char* [x+2];
for (int i = 0; i < x+2; i++)
tab[i] = new char [y+2];
而且它有效。问题是它占用了太多内存(示例数据需要 16kb,而我只能使用 5kb)是否有一种简单(或不易)的方法来实现这一点?
我能想到的唯一解决方案是使用tab[(x+2)*(y+2)]
,但我必须更改整个程序并用简单的算术填充它来计算数组中的位置,但这需要重写大量代码,所以我想避免这种情况。
编辑:5kb 是必需的,因为它是学校的项目 :) 该程序在 96 次测试(共 100 次)中完美运行,但在这一次它因为内存而停止。 edit2:如何将多个值存储在一个字符中?会不会很复杂?
【问题讨论】:
我怀疑开销可以减少到 16kb 可以容纳 5kb 的程度。 猜猜你必须想办法在任何给定时间只拥有部分数据。 你用char代表什么?根据您所代表的数据集,您可以使用单个 char 来保存多个值(例如 1x8bit char 来保存 8 个布尔值,而不是 8x8bit chars )。这需要我们知道我们正在使用的数据域。 除非您在微控制器上编程(不太可能,因为您使用的是 iostreams 和new
),在现代机器上,16 kB 的内存是非常小的内存量。事实上,new char[]
的底层内存分配器本身可能会保留超过 16 kB 以满足未来的内存请求。为什么 16 kB 是个问题,为什么 5 kB 是你的极限?
我怀疑这个练习的目的是一次只处理一小块数据,在这种情况下,我们无法回答这个问题。
【参考方案1】:
我认为最好的方法是将它封装到二维数组类中。它适用于一维数组,但您可以通过 getter 和 setter 以及您选择的索引来访问它。这是我能想到的最简单、最优雅的解决方案。
【讨论】:
封装如何更有效地利用内存? 它没有,封装是为了方便使用,否则在访问数组元素时,您需要将索引转换为某些 [actualY*width+actualX]。不好看很容易出错 同意,并且您正在寻找正确的解决方案,该解决方案以某种方式错过了所有答案 - 他在数组中有太多级别的重定向。我同意封装可以帮助发现这些问题,并且通常是无论如何都要走的路。【参考方案2】:编辑:我厌倦了看到太多不正确的答案得到支持,所以我做了一些实际的实验来证明我的主张的有效性并重写了我的答案。
tl;dr tab
是一个指向 char 的指针数组。这意味着tab
不存储char
(每个占用8 位),而是存储x
指针(通常)每个占用64 位。你需要找到一种方法来使用 tab 作为指向单个 char 数组的指针:char * tab
问题
在这个循环中:
for (int i = 0; i < x+2; i++)
tab[i] = new char [y+2];
您正在运行new
x+2 次(顺便说一句,为什么要+2??)。如您所知,new
返回 pointers 而不是 chars。它为您请求的数据类型分配内存,即char 然后返回一个指向该内存地址的指针。因此,循环中对 new 的调用分配了 8 位。由于 new 返回一个内存地址,因此您需要将其存储在某个地方。谢天谢地,您已经分配了空间来使用此行存储该地址:
tab = new char* [x+2];
现在您不是要求 new
为 char
数组保留空间,而是要求它为 char 指针数组保留空间。
在大多数现代架构中,指针需要 64 位。这些指针被存储在tab
指向的内存地址的堆上。即tab[0]
保存第一个指向 char 的指针,tab[1]
保存第二个,依此类推……这些指针保存已分配用于保存字符的额外内存的位置。
因此,总的来说,您正在为 x+2
指针分配空间:
tab = new char* [x+2];
和(x+2)*(y+2)
字符与此行:
tab[i] = new char [y+2];
如果您进行数学运算,则指针为 (x+2)*8B
plus (x+2)*(y+2)*1B
为字符。通过查看方程式,您会发现对于给定数量的字符即任何 x*y,如果 x
大于 y
,您将看到更多的内存使用。 p>
为了测试这一点,我在您的代码上运行了 valgrind massif 工具(除了我摆脱了 +2
并得到以下结果:
| x | y |useful-heap(B)|
|----|---|--------------|
| 0 | 1 | 0 |
| 1 | 0 | 8 |
| 1 | 1 | 9 |
| 1 | 2 | 10 |
| 1 | 3 | 11 |
| 2 | 0 | 16 |
| 2 | 1 | 18 |
| 2 | 2 | 20 |
| 3 | 0 | 24 |
| 3 | 1 | 27 |
| 20 | 0 | 160 |
|100 | 0 | 800 |
查看每次x
增加时内存使用量如何增加 8B。这是存储指针数组的空间分配。请注意,对于y=0
的情况,根本没有存储任何字符......所以当x=100
和y=0
使用800B 的内存时,您绝对没有任何意义。
fyi,massif 还报告了系统因最小分配大小、效率等原因而代表您进行的额外分配,但我上面给出的数字是 massif 声称您要求的确切数量
如何解决?
关键是要重新安排存储字符的方式以及处理它们的方式。您正在存储字符,因此您可以制作一个大字符数组并找到一种不同的方法来索引它们。除了字符本身之外,这不需要堆分配。
我还建议远离原始数组和指针,而改用 std::array 或 std::vector,但我假设您已被明确告知将它们用于分配...
【讨论】:
【参考方案3】:如果您使用“-Os”标志编译代码,它将针对内存进行优化。
这不是很 C++-ish,但您可以使用宏来访问和像矩阵一样排列:
#define TAB(col,row) tab[col*column_length+row]
正确的方法是创建一个类来封装所有这些。
【讨论】:
这是预处理器,对吧?如果我也写 TAB(4,6) 会有效吗? 访问数组位的正确方法#defines 几乎被普遍反对,并且在这里没有任何好处。为什么不使用类或函数呢? 编译器无法优化对占用内存的 new 的调用。 @WilliamCode 是的,这是预处理器的魔法。要访问该数组,请使用类似x=TAB(4,6);
@evan 我确实推荐了一门课程作为正确的方法。没有编译器会优化数组,它可以做的最好的事情是识别并创建某种缓存暖系统(模板元编程在这种情况下会帮助编译器,但我需要更多信息来推荐它)
【参考方案4】:
您尚未指定输入的二维数组大小(测试中的 x 和 y 是什么)。
请注意,“新”具有最小分配大小。
如果调用一堆新的 char[1] 数组(比如 x = 1, y = 10000), 那么你正在分配 min malloc size * 10000;不是 1 * 10000。 我相信最小大小是 32 或 64 字节。
如果您可以一次分配所有内存(作为单个数组分配),您将最小化所需的内存量。 例如新字符 [x * y]
【讨论】:
【参考方案5】:我相信你回答了你自己的问题,我认为没有办法让它占用更少的空间,因为 char 的可变大小会决定它。
【讨论】:
以上是关于用更少的内存在 C++ 中实现二维数组的主要内容,如果未能解决你的问题,请参考以下文章
C# 中锯齿状数组的内存分配与 C++ 中的二维数组内存分配