存储阵列的物理特性(特别是尺寸大于2)?

时间:2016-10-05 01:06:29

标签: c++ arrays

我所知道的

我知道数组int ary[]可以用等效的“指向指针”格式表示:int* ary。但是,我想知道的是,如果这两个是相同的,那么数组的物理存储方式是什么?

我曾经认为元素在ram中彼此相邻存储,就像数组ary一样:

int size = 5;
int* ary = new int[size];
for (int i = 0; i < size; i++) { ary[i] = i; }

这(我相信)存储在RAM中,如:...[0][1][2][3][4]...

这意味着我们可以随后用ary[i]替换*(ary + i),只需通过索引增加指针的位置。

问题

当我以相同的方式定义2D数组时会出现问题:

int width = 2, height = 2;
Vector** array2D = new Vector*[height]
for (int i = 0; i < width; i++) {
    array2D[i] = new Vector[height];
    for (int j = 0; j < height; j++) { array2D[i][j] = (i, j); }
}

鉴于类Vector是我将x和y存储在一个基本单位:(x,y)。

那么如何存储上述内容呢?

  1. 它无法像...[(0, 0)][(1, 0)][(0, 1)][(1, 1)]...那样在逻辑上存储,因为这意味着(1, 0)元素与(0, 1)元素相同。

  2. 它也不能存储在如下所示的二维数组中,因为物理RAM是一个8位数的1d数组:

    • ...[(0, 0)][(1, 0)]...
    • ...[(0, 1)][(1, 1)]...
  3. 它们都不能像[{1}}一样存储,因为...[&(0, 0)][&(1, 0)][&(0, 1)][&(1, 1)]...是指向&(x, y)位置的指针。这只是意味着每个内存位置只指向另一个内存位置,并且该值无法存储在任何位置。

  4. 先谢谢你。

2 个答案:

答案 0 :(得分:1)

您的问题的简短回答是:它取决于编译器。

一个更有用的答案(我希望)是你可以创建直接在内存中排列的2D数组,或者你可以创建&#34; 2D数组&#34;实际上是1D数组,有些有数据,有些有数组指针。

当您使用括号访问数组中的元素时,编译器很乐意生成正确类型的代码以取消引用和/或计算数组中元素的地址。

通常在编译时已知为2D的数组(例如int array2D [a] [b])将在没有额外指针的情况下在内存中进行布局,并且编译器知道每次有多次AND添加以获取地址访问。如果你的编译器不善于优化乘法,它会使重复访问比它们慢得多,所以在过去我们经常使用指针数学来避免乘法,如果可能的话。

有一个问题是编译器可能通过将较小的维度大小舍入到2的幂来进行优化,因此可以使用移位而不是乘法,这将需要填充位置(然后即使它们全部在一个内存块,有无意义的漏洞)。

(另外,我很确定我已经遇到了一个问题,在一个程序中,它需要知道2D数组的确是哪种方式,所以你可能需要以一种方式声明参数让编译器知道如何编写过程,例如[] []与* a []不同。显然你可以从指针数组中获得指针,如果这是你想要的 - 当然,它与它所指向的数组不同。

在你的代码中,你已经清楚地声明了一组完整的低维1D数组(在循环内部),并且你已经声明了另一个指针数组,你用它们来获取每个数组而没有多重 - 而是通过一个解除引用。所以那些东西都会记忆犹新。每个1D阵列肯定会按顺序排列在连续的内存块中。只是内存管理器完全取决于这些1D阵列相对于彼此的位置。 (我怀疑编译器是否足够聪明,可以在编译时实际执行&#34;新&#34;操作,但理论上可行,如果有的话,显然会影响/控制行为。)

使用额外的指针数组清楚地避免了永远的倍增。但是它需要更多的空间,并且对于顺序访问实际上使得访问更慢和更大(额外的取消引用)而不是维持单个指针和一个解除引用。

即使1D阵列的DO有时也会连续,你可能会使用同一个内存管理器打破另一个线程,运行一个&#34; new&#34;而你的&#34;新&#34;循环内部正在重复。

答案 1 :(得分:1)

OP正在为动态分配的动态分配数组指针阵列做斗争。这些分配中的每一个都是自己的内存块,位于存储的某个位置。除了由外部数组中的指针建立的逻辑连接之外,它们之间没有任何连接。

试图想象这个我们做

int ** twodee;
twodee = new int*[4];
for (int i = 0; i < 4; i++)
{
    twodee[i] = new int[4];
}

然后

int count = 1;
for (int i = 0; i < 4; i++)
{
    for (int j = 0; j < 4; j++)
    {
        twodee[i][j] = count++;
    }
}

所以我们最终应该twodee看起来像

 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

正确?

逻辑上,是的。但是在记忆中twodee可能看起来像这个疯狂的混乱:

batsmurph crazy mess

你无法真实地预测你的记忆将会在哪里,你无论是内存管理器处理分配还是已经存储在哪里,它可能对你的记忆有效。这使得在脑中放置动态分配的多维数组几乎浪费时间。

当你深入了解现代CPU可以为你做什么的时候,这有很多问题。 CPU必须大量跳转,当它跳跃时,它能够预测和预加载具有你不太可能在不久的将来需要的内存的缓存。这意味着你的千兆赫计算机必须坐在那里等待你的兆赫RAM,而不是它应该有的。

尽可能通过分配单个连续的内存块来尽量避免这种情况。您可以选择一些将一维内存映射到其他维度的额外代码,但不会丢失任何CPU时间。无论如何,只要你编译[i][j],C ++就会为你生成所有的映射数学。