如何在 C 中正确设置、访问和释放多维数组?

How do I correctly set up, access, and free a multidimensional array in C?

提问人:Mike 提问时间:9/17/2012 更新时间:7/23/2019 访问量:7634

问:

我已经看到了很多关于 C 语言中多维数组的“我的代码有什么问题”的问题。出于某种原因,人们似乎无法理解这里发生的事情,所以我决定回答这个问题作为其他人的参考:

如何在 C 中正确设置、访问和释放多维数组?

如果其他人有有用的建议,请随时发帖!

c 动态 多维阵列 malloc

评论


答:

8赞 Mike 9/17/2012 #1

从静态上讲,这很容易理解:

int mtx[3][2] = {{1, 2},
                 {2, 3},
                 {3, 4}};

这里没什么复杂的。3行2列;第一列中的数据: ;第二列中的数据:。 我们可以通过相同的结构访问元素:1, 2, 32, 3, 4

for(i = 0; i<3; i++){
    for(j = 0; j<2; j++)
        printf("%d ", mtx[i][j]);
    printf("\n");
}
//output
//1 2
//2 3
//3 4

现在让我们从指针的角度来看一下:

括号是一个非常好的结构,可以帮助简化事情,但是当我们需要在动态环境中工作时,它没有帮助,因此我们需要从指针的角度来考虑这一点。如果我们想存储整数的“行”,我们需要一个数组:

int row[2] = {1,2};

你知道吗?我们可以像指针一样访问它。

printf("%d, %d\n",*row,*(row+1));   //prints 1, 2
printf("%d, %d\n",row[0],row[1]);   //prints 1, 2

现在,如果我们不知道一行中的值数,如果我们有一个指向 int 的指针,我们可以将这个数组设置为动态长度,并给它一些内存:

int *row = malloc(X * sizeof(int));  //allow for X number of ints
*row = 1;        //row[0] = 1
*(row+1) = 2; //row[1] = 2
…
*(row+(X-1)) = Y; // row[x-1] = Some value y

所以现在我们有一个动态的一维数组;单行。但是我们想要很多行,而不仅仅是一行,我们不知道有多少行。这意味着我们需要另一个动态的一维数组,该数组的每个元素都将是一个指向一行的指针。

//we want enough memory to point to X number of rows
//each value stored there is a pointer to an integer
int ** matrix = malloc(X * sizeof(int *));

//conceptually:
(ptr to ptr to int)     (pointer to int)
   **matrix ------------> *row1 --------> [1][2]
                          *row2 --------> [2][3]
                          *row3 --------> [3][4]

现在剩下要做的就是编写代码来执行这些动态分配:

int i, j, value = 0;

//allocate memory for the pointers to rows
int ** matrix = malloc(Rows * sizeof(int*));

//each row needs a dynamic number of elements
for(i=0; i<Rows; i++){
    // so we need memory for the number of items in each row… 
    // we could call this number of columns as well
    *(matrix + i) = malloc(X * sizeof(int));

     //While we’re in here, if we have the items we can populate the matrix
    for(j=0; j<X; j++)
        *(*(matrix+i)+j) = value; // if you deference (matrix + i) you get the row
                                  // if you add the column and deference again, you
                                  // get the actual item to store (not a pointer!)
}

现在要做的最重要的事情之一是确保我们在完成后释放内存。每个级别的调用数应相同,并且调用应按 FILO 顺序排列(与 malloc 调用相反):malloc()free()

for(i=0; i<Rows; i++) 
    free(*(matrix + i));
free(matrix);

//set to NULL to clean up, matrix points to allocated memory now so let’s not use it!
matrix = NULL; 

评论

2赞 Lundin 9/17/2012
很好的答案,但请不要使用指针到指针的语法,它会创建分段的多点。数组与静态分配的数组不兼容,也不与 C 标准库函数(如 memcpy、memset、bsearch、qsort 等)兼容。请参阅 Jens 的回答,了解分配动态多调光的首选方法。阵 列。
0赞 Mike 9/18/2012
@Lundin - 一个很好的观点,我选择使用指针到指针语法,因为这是我当时被教导的方式,我认为它仍然以这种方式教授(基于我在 SO 上看到的问题)
1赞 Eric Postpischil 9/18/2012
它不是“语法”。语法是关于语言的规则,或者通俗地说,是关于语言的特定样本的规则。语法问题就是表达和交流问题。指针到指针方法的问题不仅在于它使用的语言,还在于它在程序中导致的浪费操作:使用的内存多于必要的内存(用于不需要的指针和单独分配每行时的额外记帐),使用的时间超过必要的时间(每次访问一行时加载指针和额外的分配调用), 并且代码比必要的更复杂。
0赞 Lundin 9/18/2012
@EricPostpischil 它是语法,因为使用的类型是而不是 .int**int (*)[]
3赞 Eric Postpischil 9/18/2012
@Lundin:这就像说巴黎和热核弹之间的区别在于拼写,因为一个拼写为“巴黎”,另一个拼写为“热核弹”。事实上,核心区别或影响最大的不是语法。语法只是一种交流方式;真正的问题才是被传达的东西。另一种看待这个问题的方法是将其翻译成另一种语言:假设语法被交换,但底层行为保持不变。那会更好吗?不,双指针问题仍然存在。
30赞 Jens Gustedt 9/17/2012 #2

在 C99 之后的 C 语言中,即使是动态多维数组也可以很容易地一次性分配并释放:mallocfree

double (*A)[n] = malloc(sizeof(double[n][n]));

for (size_t i = 0; i < n; ++i)
  for (size_t j = 0; j < n; ++j)
      A[i][j] = someinvolvedfunction(i, j);

free(A);

评论

0赞 Lundin 9/17/2012
这是首选方式,避免指针到指针语法。我不确定,但我相信这在 C90 中也有效?数组指针肯定在 C99 之前就存在了吗?至少“残缺”的数组起作用了,即.double* A = malloc(x*y*sizeof(double));
1赞 Jens Gustedt 9/17/2012
@Lundin,不,不幸的是,声明部分只有在编译时常量(基本上是宏或常量)时才起作用。double (*A)[n]nenum
1赞 Lundin 9/18/2012
啊哈,好吧,我想使用编译时已知的大小进行动态分配没有多大意义:)虽然,“n”是强制性的吗?你不会写吗?double (*A)[] =
2赞 Steve Jessop 9/18/2012
@Lundin:有时使用编译时已知的大小进行动态分配是有意义的,因为多维数组可以很容易地破坏堆栈。
1赞 Scooter 9/22/2012
@JensGustedt 你能从函数中返回 A 吗,如果是这样,返回类型是什么?
17赞 Steve Jessop 9/17/2012 #3

在 C89 中,至少有四种不同的方法可以创建或模拟多维数组。

一种是“单独分配每一行”,Mike在他的回答中描述了这一点。它不是一个多维数组,它只是模仿一个数组(特别是它模仿访问元素的语法)。在每行都有不同大小的情况下,它可能很有用,所以你不是在表示一个矩阵,而是在表示一个具有“参差不齐边缘”的东西。

一种是“分配多维数组”。它看起来像这样:

int (*rows)[NUM_ROWS][NUM_COLS] = malloc(sizeof *rows);
...
free(rows);

则访问元素 [i,j] 的语法为 。在 C89 中,必须在编译时知道 和。这是一个真正的二维数组,并且是指向它的指针。(*rows)[i][j]NUM_COLSNUM_ROWSrows

一种是“分配行数组”。它看起来像这样:

int (*rows)[NUM_COLS] = malloc(sizeof(*rows) * NUM_ROWS);
...
free(rows);

则访问元素 [i,j] 的语法为 。在 C89 中,必须在编译时知道。这是一个真正的二维阵列。rows[i][j]NUM_COLS

一种是“分配一个一维数组并假装”。它看起来像这样:

int *matrix = malloc(sizeof(int) * NUM_COLS * NUM_ROWS);
...
free(matrix);

则访问元素 [i,j] 的语法为 。这(当然)不是真正的二维数组。在实践中,它具有与一个相同的布局。matrix[NUM_COLS * i + j]

评论

0赞 Lundin 9/18/2012
“分配一个行数组”,这不就是:分配一个数组数组,然后分配一个数组指针指向第一个对象/数组吗?我自己总是使用这种形式,尽管也许“2D”指针在风格上更正确?
1赞 Steve Jessop 9/18/2012
@Lundin:两者兼而有之。在所有形式中(可以说是扁平化数组除外),每一行都是一个数组,因此行的数组就是数组的数组。但是,由于多维数组无论如何都是数组的数组(根据标准中的定义),因此从技术上讲,我的标题没有区分它们。对我来说,重点的差异是显而易见的,也许对其他人来说不是。
1赞 Lundin 9/18/2012
经过一番思考后,我肯定会说第一个版本是首选,因为它将使编译器或静态分析工具有机会通过检测和警告不正确的隐式类型转换来强制执行“更强的类型”。第 2 和第 3 种形式可能会意外地与普通的一维数组或普通指针混淆,而没有任何工具可以检测可能的错误。
1赞 Steve Jessop 9/18/2012
没有不尊重你的分析,我认为这可能是正确的,但如果我更喜欢某样东西,我只是说我更喜欢它,我尽量记住不要说它“是首选”。我的担忧可能与其他人的担忧不同,特别是在 C89 中,对编译时已知的边界的需求非常有限。第一个选项的语法并不那么吸引人,但它肯定允许编译器在两个维度上进行静态边界检查,而不仅仅是一个维度。
1赞 Steve Jessop 10/14/2015
@mk..:第一个。
1赞 Serge Ballesta 10/10/2016 #4

如果你想使用 typedef'd 数组,那就更简单了。

假设你的代码中有typedef int LabeledAdjMatrix[SIZE][SIZE];

然后,您可以使用:

LabeledAdjMatrix *pMatrix = malloc(sizeof(LabeledAdjMatrix));

然后你可以写:

for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) (*parr)[i][j] = k++; /* or parr[0][i][j]... */
}

因为是指向 u 矩阵的指针,并且优先级低于pArr*[];

这就是为什么一个常见的模式习惯语是 typedef 行:

typedef int LabeledAdjRow[SIZE];

然后你可以写:

LabeledAdjRow *pMatrix = malloc(sizeof(LabeledAdjRow) * SIZE);
for (i=0; i<SIZE; i++) {
    for (j=0; j<SIZE; j++) parr[i][j] = k++;
}

您可以直接执行以下操作:memcpy

LabeledAdjRow *pOther = malloc(sizeof(LabeledAdjRow) * SIZE);
memcpy(pOther, pMatrix, sizeof(LabeledAdjRow) * SIZE);

评论

0赞 Serge Ballesta 10/10/2016
我知道对于当前的问题来说,这是一个糟糕的答案,但它直接针对另一个被关闭为重复的问题......
1赞 Shawn T 7/23/2019 #5

根据 Jen 的回答,如果你想为 3D 阵列分配空间,以同样的方式,你可以这样做

int(*A)[n][n] = malloc(sizeof(int[num_of_2D_arrays][n][n]));

for (size_t p = 0; p < num_of_2D_arrays; p++)
  for (size_t i = 0; i < n; i++)
    for (size_t j = 0; j < n; j++)
      A[p][i][j] = p;

for (size_t p = 0; p < num_of_2D_arrays; p++)
    printf("Outter set %lu\n", p);
    for (size_t i = 0; i < n; i++)
      for (size_t j = 0; j < n; j++)
        printf(" %d", A[p][i][j]);
      printf("\n");

free(A);