处理堆分配内存时(* prt)[N] [N]如何工作?

时间:2018-05-09 12:48:35

标签: c arrays multidimensional-array malloc c99

今天,一位同事在工作中向我展示了一种声明2D阵列的方式,我可以线性分配它,但仍然使用2D方括号([][])符号来访问元素。

例如:

#include <stdio.h>
#include <stdlib.h>

#define SIZE 2

int main () {
  int (*a)[SIZE][SIZE] = malloc (sizeof (int) * SIZE * SIZE);

  for (int i = 0; i < SIZE; i++) {
    for (int j = 0; j < SIZE; j++) {
      (*a)[i][j] = 0;
    }
  }

  (*a)[0][1] = 100;

  /* should yield:
   *   0
   *   100
   *   0
   *   0
   */
  for (int i = 0; i < SIZE; i++) {
    for (int j = 0; j < SIZE; j++) {
      printf ("%d\n", (*a)[i][j]);
    }
  }

  free (a);

  return EXIT_SUCCESS;
}

这与计算索引然后执行指针关节(例如*(a + (x * SIZE + y))或更多的a[x * SIZE + y])以访问元素形成对比。

关键部分是指针x的形状声明(例如(*x)[][]),它似乎将此信息编码为x指向的值的类型。

除此之外,我不明白这是如何工作的。这种符号究竟在做什么?是语法糖吗?它看起来与数组的动态堆栈分配是一致的(参见Array size at run time without dynamic allocation is allowed?作为其中的一个示例),但显然这种分配发生在堆上。

我已经查找了有关指针的这种表示法/声明的更多信息,但除了术语元素类型之外找不到更多 - 但是我是不确定这是否相关。

编辑#1:

我应该提到这个问题是在使用堆的情况下,而不是堆栈。我知道基于堆栈的数组动态分配,但我所做的工作专门研究动态内存分配。

4 个答案:

答案 0 :(得分:1)

没有错,但不是更常见(和惯用的方式)。要声明大小为N的动态数组,请使用:int *arr = malloc(N * sizeof(int));。实际上,这将arr声明为指向N int 数组的第一个元素的指针。 2D数组是一个数组数组,因此要声明一个二维数组N * N,更常见的方法是:

int (*arr)[N] = malloc(N * N * sizeof(int));

这实际上将arr声明为指向N int的N个数组的第一个元素的指针。然后,您通常可以使用arr[i][j]

那令人惊奇的是什么int (*a)[SIZE][SIZE] = malloc (sizeof (int) * SIZE * SIZE);

将arr声明为指向整数的二维数组NxN数组的第一个(和单个)元素的指针。好消息是声明对于所有维度的大小都是明确的,但缺点是您必须始终取消引用它:(*arr)[i][j]这与[]运算符的每个定义没有区别 C 来自arr[0][i][j]

这只不过是我自己的意见,但我强烈建议你坚持第一种方法。第一个和单个元素技巧可能会扰乱您代码的任何未来读者或维护者,因为它不是惯用语。

答案 1 :(得分:1)

int (*a)[SIZE][SIZE]

a声明为指向SIZE SIZE int数组SIZE == 3的指针 - 假设为 +---+ +---+---+---+ a: | | -------> | | | | +---+ +---+---+---+ | | | | +---+---+---+ | | | | +---+---+---+ ,您可以得到以下内容:

(*a)[i][j]

(实际上,布局是严格线性的,但我们现在将使用此表示法)。

要访问指向数组的任何元素,我们写a - 我们必须明确取消引用a,因为我们不想索引到{{ 1}},我们想要索引a 指向的内容。

请记住,a[i]被定义为*(a + i) - 从该地址给出地址a,偏移i个元素(不是字节!)并遵循结果。因此,(*a)[i][j]相当于a[0][i][j]

现在,如果a指向int的3x3数组,则a + 1指向int下一个3x3数组:< / p>

   +---+          +---+---+---+
a: |   | -------> |   |   |   |
   +---+          +---+---+---+
                  |   |   |   |
                  +---+---+---+
                  |   |   |   |
                  +---+---+---+
a + 1: ---------> |   |   |   |
                  +---+---+---+
                  |   |   |   |
                  +---+---+---+
                  |   |   |   |
                  +---+---+---+

我们将(*(a + 1))[i][j]访问,或只是a[1][i][j]

现在,为什么要首先使用指向数组的指针?在这种情况下,我们将动态分配数组,如果a)我们不知道在运行时需要多少SIZExSIZE个数组,或者b)如果结果,我们会这样做数组太大而无法分配为auto变量,或c)如果我们想根据需要扩展或缩小SIZExSIZE数组的数量。

这种分配多维数组的方法如何工作?我们首先分配一个N - T元素数组:

T *arr = malloc( sizeof *arr * N );

sizeof *arr相当于sizeof (T),因此我们为N T类型的T对象留出空间。

现在让我们用数组类型R [M]替换R (*arr)[M] = malloc( sizeof *arr * N );

sizeof *arr

sizeof (R [M])相当于N,因此我们为R [M]N对象留出空间 - IOW,M {{ 1}} - R的元素数组。我们动态创建了R a[M][N]的等价物。

我们也可以把它写成

R (*arr)[M] = malloc( sizeof (R) * M * N );

虽然我更喜欢使用sizeof *arr;你会在一秒钟内看到原因。

现在,我们可以将R替换为另一个数组类型S [L]

S (*arr)[L][M] = malloc( sizeof *arr * N );

sizeof *arr相当于sizeof (S [L][M]),因此我们为N类型的S [L][M]个对象分配了足够的空间,或N {{1由L的{​​{1}}数组组成。我们动态创建了M的等价物。

动态分配1D,2D和3D阵列的语义完全相同 - 所有改变的类型都是类型。通过每次使用S,我只需要跟踪我需要该类型的元素数量。

答案 2 :(得分:0)

int (*a)[SIZE][SIZE] = malloc (sizeof (int) * SIZE * SIZE);的作用是声明指向二维整数数组的指针。这只有在你故意想要在堆中而不是在堆栈中分配空间时才有用(例如,如果在编译时数组的维度是未知的)你将取消引用指针并像访问它一样访问它一个普通的二维数组。

您可以通过将变量声明为指针数组来跳过解除引用步骤,每个指针指向一个标准的整数数组int *a[SIZE],甚至指向int **a。在这两种情况下,您都可以使用括号表示法a[x][y]访问任何值,而无需在之前取消引用a

如果您在编译时知道数组的大小,并且不需要在堆中分配它,则可以像这样声明数组:

int a[SIZE][SIZE];

它在分配堆栈空间时更短,效率更高。

您始终可以使用[][]访问数组。 你必须记住,C中的所有内容都与内存地址偏移有关。如果您有一个声明为int a[4]的整数数组,并且您使用方括号(例如a[3])访问它,那么您告诉处理器获取a的内存地址并应用{的偏移量{1}}。您可以使用3 * sizeof(int)或甚至*(&a + 3)来访问相同的元素,因为获取地址并添加偏移量与获取偏移量和添加地址相同。

因此,当您使用3[a]时,编译器与上面完全相同,只有更多维度。因此,您不需要执行a[2][3],因为当您执行a[x * SIZE + y]时,这正是编译器为您所做的。

编辑:正如评论中指出的那样,实际上指针不一定存储内存引用,尽管这绝对是最常见的实现。

我希望我的解释清楚。

答案 3 :(得分:0)

int (*a)[SIZE][SIZE]是类型为int[SIZE][SIZE]的数组的数组指针。这是一种特殊的指针,用于指向整个数组,但其他方式与任何其他指针一样。所以当你写(*a)[i][j]时,你说“给我指针的内容(2D数组),然后在那些内容中给我项目编号[i] [j]”。

但由于数组指针的行为与其他指针相似,因此您可以使用它指向第一个元素而不是整个2D数组。 (就像您可以使用int*指向int[n]数组的第一项一样。)这是使用省略最左侧维度的技巧完成的:int (*a)[SIZE] = ...。现在指向数组数组中的第一个1D数组。现在,您可以将其用作a[i][j],这样更易​​读,更方便。

数组指针,上面的技巧,以及如何使用它们将2D数组动态分配为一块内存,这些都在我对Correctly allocating multi-dimensional arrays的回答中得到解决。