3D数组在C语言中是如何存储的?

41

我知道在C语言中,数组是按行主序分配的。因此,对于一个2 x 3的数组:

0  1
2  3
4  5

存储在内存中的形式为

0 1 2 3 4 5

然而,如果我有一个 2 x 3 x 2 的数组:

0  1
2  3
4  5

并且

6  7
8  9
10 11

这些是如何在内存中存储的?只是按顺序连续存储吗,就像:

0 1 2 3 4 5 6 7 8 9 10 11

还是其他方式?或者这取决于什么?


10
问题中的第一个数组实际上是3行2列,例如 int a[3][2]; - Alexey Kukanov
@Alexey:很有趣-也许这就是我困惑的根源。在C中,数组应该声明为array[ny][nx],其中nynx是y和x方向上元素的数量。此外,这是否意味着我的3D数组应该声明为array[nz][ny][nx] - robintw
8个回答

27

在低级别编程中,并不存在多维数组这种概念。实际上只有一块足够大的内存块来存储指定数量的元素。在C语言中,多维数组的概念是一个元素也是数组的数组。所以如果你这样做:

int array[2][3];

从概念上讲,你最终会得到:

array[0] => [0, 1, 2]
array[1] => [0, 1, 2]

这将导致元素在内存中被连续排列,因为array [0]array [1]实际上并不持有任何数据,它们只是对两个内部数组的引用。请注意,这意味着只有[0, 1, 2]条目实际上在内存中占据空间。如果你将这种模式扩展到下一个维度,你会发现:

int array[2][3][2];

...会给您提供类似以下的结构:

array[0] => [0] => [0, 1]
            [1] => [0, 1]
            [2] => [0, 1]
array[1] => [0] => [0, 1]
            [1] => [0, 1]
            [2] => [0, 1]

这将继续按顺序在内存中排列元素(如上所述,只有[0, 1]条目实际占用内存空间,其他所有内容都只是对这些条目之一的引用的一部分)。 可以看到,无论您有多少维,这种模式都将继续。

仅供娱乐:

int array[2][3][2][5];

给你:

array[0] => [0] => [0] => [0, 1, 2, 3, 4]
                   [1] => [0, 1, 2, 3, 4]
            [1] => [0] => [0, 1, 2, 3, 4]
                   [1] => [0, 1, 2, 3, 4]
            [2] => [0] => [0, 1, 2, 3, 4]
                   [1] => [0, 1, 2, 3, 4]
array[1] => [0] => [0] => [0, 1, 2, 3, 4]
                   [1] => [0, 1, 2, 3, 4]
            [1] => [0] => [0, 1, 2, 3, 4]
                   [1] => [0, 1, 2, 3, 4]
            [2] => [0] => [0, 1, 2, 3, 4]
                   [1] => [0, 1, 2, 3, 4]

21

所有的“维度”在内存中是连续存储的。

考虑以下内容:

    int arr[4][100][20];
你可以说,arr[1]arr[2](类型为int[100][20])是连续的,
或者arr[1][42]arr[1][43](类型为int[20])是连续的,
或者arr[1][42][7]arr[1][42][8](类型为int)是连续的。

13

没错,你说得对-它们是连续存储的。考虑这个例子:

#include <stdio.h>

int array3d[2][3][2] = {
  {{0, 1}, {2, 3}, {3, 4}},
  {{5, 6}, {7, 8}, {9, 10}}
};

int main()
{
  int i;
  for(i = 0; i < 12; i++) {
    printf("%d ", *((int*)array3d + i));
  }
  printf("\n");
  return 0;
}

输出:

0 1 2 3 3 4 5 6 7 8 9 10


你好,我只是好奇如何将这个三维数组可视化为矩阵,并且它是如何分配元素的? - bhansa

9

是的,它们只是按连续顺序存储。您可以像这样进行测试:

#include <stdio.h>

int main (int argc, char const *argv[])
{
  int numbers [2][3][4] = {{{1,2,3,4},{5,6,7,8},{9,10,11,12}}
                          ,{{13,14,15,16},{17,18,19,20},{21,22,23,24}}};

  int i,j,k;

  printf("3D:\n");
  for(i=0;i<2;++i)
    for(j=0;j<3;++j)
      for(k=0;k<4;++k)
        printf("%i ", numbers[i][j][k]);

  printf("\n\n1D:\n");
  for(i=0;i<24;++i)
    printf("%i ", *((int*)numbers+i));

  printf("\n");

  return 0;
}

这意味着对于具有维度(N,M,L)的多索引数组的访问将被转换为以下形式的一维访问:

array[i][j][k] = array[M*L*i + L*j + k]

6
我认为你已经自己回答了这个问题。多维数组是按行主序存储的。
请查看 ANSI C 规范第 3.3.2.1 节(还有一个具体示例):
连续的下标运算符指定了多维数组对象的成员。如果 E 是一个 n 维数组(n=2),它的维数是 i x j “x...x”k,则 E(用作其他而不是左值)被转换为指向一个(n-1)维数组,其维数是 j“x...x”k。如果对此指针显式地或隐式地应用一元 * 运算符,或者由于下标运算而隐含地应用一元 * 运算符,则结果是指向的(n-1)维数组,如果将其用作其他而不是左值,则本身也会被转换为指针。由此可知,数组是按行主序存储的(最后一个下标变化最快)。
对于你的示例,你可以尝试一下并观察:http://codepad.org/10ylsgPj

3
假设你有一个数组 char arr[3][4][5],它是一个由3个包含4个包含5个字符的数组组成的数组。
为了简单起见,假设在arr[x] [y] [z]中的值为xyz,并且在arr[1] [2] [3]中存储123
因此,内存布局如下:
  |  00  01  02  03  04  05  06  07  08  09  10  11  12  13  14  15  16  17  18  19
--+--------------------------------------------------------------------------------   
00| 000 001 002 003 004 010 011 012 013 014 020 021 022 023 024 030 031 032 033 034 
20| 100 101 102 103 104 110 111 112 113 114 120 121 122 123 124 130 131 132 133 134 
40| 200 201 202 203 204 210 211 212 213 214 220 221 222 223 224 230 231 232 233 234
arr[0]arr[1]arr[2] 依次出现,但是每个元素都是 char[4][5] 类型的(这些是表中的三行)。 arr[x][0] - arr[x][3] 也依次出现,它们中的每个元素都是类型为 char[5] 的(这些是表中每行的四个部分,000-004 是 arr[0][0] 的一个元素)。 arr[x][y][0] - arr[x][y][4] 是 5 个字节,依次出现。

2
回答主题问题的OP评论(会有些长,所以我决定回答而不是评论):
在C语言中,应该将数组声明为array[ny][nx],其中nynx是y和x方向的元素数。此外,这是否意味着我的3D数组应该声明为array[nz][ny][nx]
在数学中,MxN矩阵具有M行和N列。矩阵元素的通常表示法是a(i,j),1<=i<=M, 1<=j<=N。因此,您问题中的第一个矩阵是一个3x2矩阵。
它确实与通常用于GUI元素的符号不同。800x600位图在水平方向(沿X轴)有800个像素,在垂直方向(沿Y轴)有600个像素。如果有人想将其描述为矩阵,则在数学符号中,它将是一个600x800矩阵(600行,800列)。
现在,在C中,多维数组存储在内存中的方式是a[i][j+1]紧邻a[i][j],而a[i+1][j]则相距N个元素。通常说“最后一个下标变化最快”,或者经常被称为“按行存储”:2D矩阵中的一行(即具有相同第一个索引的元素)在内存中连续放置,而列(具有相同第二个索引的元素)则由相距较远的元素组成。这对于性能考虑很重要:访问邻居元素通常更快(由于硬件缓存等),因此例如嵌套循环应该组织为最内层循环迭代最后一个索引。
回到问题:如果您对2D数组的心理图像(抽象)是笛卡尔坐标系中的晶格,则可以将其视为C中的array[NY][NX]。但是,如果您需要将真实的2D或3D数据描述为数组,则索引的选择可能取决于其他事情:数据格式,方便的符号,性能等。例如,如果位图的内存表示形式是您需要使用的格式中的array[NX][NY],则将以这种方式声明它,甚至可能不需要知道位图变得有点“转置” :)

-3

3D数组是扩展的2D数组。

例如,我们有一个数组 - int arr(3)(5)(6);

这是一个由两个2D数组组成的数组,其中每个数组都有4行3列的2D数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接