【问题标题】:How do I correctly set up, access, and free a multidimensional array in C?如何在 C 中正确设置、访问和释放多维数组?
【发布时间】:2012-09-09 20:49:04
【问题描述】:

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

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

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

【问题讨论】:

    标签: c dynamic multidimensional-array malloc


    【解决方案1】:

    在 C99 以来的 C 中,即使是动态多维数组也可以轻松地使用 malloc 一次性分配并使用 free 释放:

    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);
    

    【讨论】:

    • 这是首选方式,避免使用指针到指针的语法。我不确定,但我相信这也适用于 C90?数组指针肯定在 C99 之前就存在了吗?至少“损坏”的数组有效,即double* A = malloc(x*y*sizeof(double));
    • @Lundin,不幸的是,声明部分 double (*A)[n] 仅在 n 是编译时常量时才有效,基本上是宏或 enum 常量。
    • 啊哈,我想用编译时已知的大小动态分配没有多大意义 :) 虽然,'n' 是强制性的吗?你不能写double (*A)[] =吗?
    • @Lundin:有时使用编译时已知的大小动态分配是有意义的,因为多维数组很容易爆栈。
    • @JensGustedt 你能从函数中返回 A,如果可以,返回类型是什么?
    【解决方案2】:

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

    一个是“分别分配每一行”,由 Mike 在他的回答中描述。它不是多维数组,它只是模仿一个(特别是模仿访问元素的语法)。它在每行具有不同大小的情况下很有用,因此您表示的不是矩阵,而是具有“参差不齐的边缘”的东西。

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

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

    那么访问元素[i,j]的语法是(*rows)[i][j]。在 C89 中,NUM_COLSNUM_ROWS 在编译时必须是已知的。这是一个真正的二维数组,rows 是指向它的指针。

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

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

    那么访问元素[i,j]的语法是rows[i][j]。在 C89 中,NUM_COLS 必须在编译时已知。这是一个真正的二维数组。

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

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

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

    【讨论】:

    • “分配一个行数组”,这不就是:分配一个数组数组,然后分配一个数组指针指向第一个对象/数组吗?我自己总是使用这种形式,但也许“2D”指针在风格上更正确?
    • @Lundin:两者兼而有之。在所有形式(可以说是扁平数组除外)中,每一行都是一个数组,因此行数组就是数组数组。但是由于多维数组 is 无论如何都是数组的数组(根据标准中的定义),我的标题在技术上并不区分它们。对我来说,重点的区别很明显,也许对其他人来说不是。
    • 经过深思熟虑,我肯定会说第一个版本是首选,因为它可以让编译器或静态分析工具有机会通过检测和警告来强制执行“更强的类型”反对不正确的隐式类型转换。第二种和第三种形式可能会意外地与普通的一维数组或普通指针混淆,而任何工具都没有机会检测到可能的错误。
    • 没有不尊重您的分析,我认为这可能是正确的,但如果我更喜欢某件事,我只是说我喜欢它,我尽量记住不要说它“是首选”。我的担忧可能与其他人的不同,特别是在 C89 中,对编译时已知边界的需求非常有限。第一个选项的语法并不是那么吸引人,但它肯定允许编译器在两个维度上进行静态边界检查,而不仅仅是一个维度。
    • @mk..:第一个。
    【解决方案3】:

    静态地说,这很容易理解:

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

    这里没有什么复杂的。 3行2列;第一列数据:1, 2, 3;第二列中的数据:2, 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!)
    }
    

    现在要做的最重要的事情之一是确保在完成后释放内存。每个级别的malloc() 应该有相同数量的free() 调用,并且调用应该是FILO 顺序(malloc 调用的逆向):

    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; 
    

    【讨论】:

    • 好答案,但请不要使用指针到指针的语法,它会创建分段的多维度。与静态分配的数组不兼容的数组,也不与 memcpy、memset、bsearch、qsort 等 C 标准库函数兼容。有关分配动态多维度的首选方法,请参阅 Jens 的答案。数组。
    • @Lundin - 很好,我选择使用指针对指针的语法,因为我以前就是这样教它的,我认为它仍然是这样教的(基于我在 SO 上看到的问题)
    • 它不是“语法”。语法是关于语言的规则,或者通俗地说,是一种特定的语言样本。句法问题是表达和交流的问题。指针到指针方法的问题不仅在于它使用的语言,还在于它在程序中造成的浪费行为:使用了比需要更多的内存(用于不需要的指针以及单独分配每一行时的额外会计) ,使用了不必要的时间(每次访问一行时加载一个指针和额外的分配调用),并且代码比必要的复杂。
    • @EricPostpischil 是语法,因为使用的类型是int**而不是int (*)[]
    • @Lundin:这就像说巴黎和热核弹的区别在于拼写,因为一个拼写为“Paris”,另一个拼写为“thermonuclear bomb”。事实上,最核心的差异或影响最大的并不是语法。语法只是一种交流方式;真正的问题是正在传达的东西。另一种看待这一点的方法是将其翻译成另一种语言:假设语法被交换但底层行为保持不变。那会更好吗?不,双指针问题仍然存在。
    【解决方案4】:

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

    假设你的代码中有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]... */
    }
    

    因为pArr 是指向矩阵的指针,而* 的优先级低于[]

    这就是为什么模式常见的习惯用法是 typedef the row:

    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);
    

    【讨论】:

    • 我知道这对当前问题来说是一个糟糕的答案,但它直接针对作为重复关闭的另一个 question....
    【解决方案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);
    

    【讨论】:

      猜你喜欢
      • 2014-11-05
      • 2015-05-10
      • 2010-12-16
      • 1970-01-01
      • 1970-01-01
      • 2012-10-08
      • 2012-01-26
      • 2014-10-03
      • 2011-08-05
      相关资源
      最近更新 更多