【问题标题】:Why does a 2D array behave like a 1D array of pointers instead of a 1D array of integers?为什么二维数组的行为类似于一维指针数组而不是一维整数数组?
【发布时间】:2018-04-06 23:17:28
【问题描述】:

我一直在研究和测试我的 C 知识(我是一名新计算机工程专业的学生),但遇到了一个我无法弄清楚的问题。

当尝试将二维数组传递给函数时,我了解到动态分配的数组不能这样做,因为编译器需要知道数组[][列]。但是,我了解到 2D 数组存储一个 1D 数组,其中每个新行的元素紧跟前一行的元素。当我将数组名称作为指向数组的指针传递给函数时,情况似乎就是这样,并且我的代码工作正常。但是,在声明 2D 数组的函数中,它表现为一个指针数组。

#include <stdio.h>

void printArray(int *A, int* dimA) {
    for(int i = 0; i < dimA[0]; ++i) {
        for(int j = 0; j < dimA[1]; ++j) {
            printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
        }
    printf("\n\n");
    }
return;
}

int main(){
    int A[2][2] = {{1,2},{3,4}};
    int dimA[2] = {2,2};//dimensions of the array
    int i, j; 

    for(i = 0; i < dimA[0]; ++i) {
        for(j = 0; j < dimA[1]; ++j) {
            printf("%3d", *(A[i] + j)); //This would work if the elements of A[] are pointers
        }
        printf("\n\n");
    }

    for(i = 0; i < dimA[0]; ++i) {  //Same code as printArray function
        for(j = 0; j < dimA[1]; ++j) {
            printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
        }
        printf("\n\n");
    }

    printArray(A, dimA);
    return 0;
}

当数组被视为指针数组时,以下代码在 main() 中正确输出数组,但当被视为一维整数数组时则不正确。但是,当我将相同的数组作为指针传递给 printArray 函数时,我可以将其视为一维整数数组并且它可以工作。任何帮助将不胜感激(我已经明白我可以使用指针数组,但我真的很想了解问题所在)。谢谢!

【问题讨论】:

    标签: arrays c pointers multidimensional-array implicit-conversion


    【解决方案1】:

    根据 C 标准(6.3.2.1 左值、数组和函数指示符)

    3 除非它是 sizeof 运算符的操作数或一元 & 运算符,或者是用于初始化数组的字符串文字,an 具有“类型数组”类型的表达式被转换为 类型为“pointer to type”的表达式,指向初始 数组对象的元素并且不是左值。如果数组 对象有注册存储类,行为未定义。

    因此在第一个 for 循环中

    for(i = 0; i < dimA[0]; ++i) {
        for(j = 0; j < dimA[1]; ++j) {
            printf("%3d", *(A[i] + j)); //This would work if the elements of A[] are pointers
        }
        printf("\n\n");
    }
    

    表达式A[i] 的类型为int[2]。转换为指针时,它的类型为int *。因此,对于每个 i,表达式 A[i] 指向数组 A 的每个“行”的第一个元素。

    表达式A[i] + j 指向每行的第j 个元素。因此,取消引用指针,您将获得数组第 i 行的第 j 个元素。

    在第二个循环中

    for(i = 0; i < dimA[0]; ++i) {  //Same code as printArray function
        for(j = 0; j < dimA[1]; ++j) {
            printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
        }
        printf("\n\n");
    }
    

    表达式A[i*dimA[1] + j] 具有int * 类型并指向数组的i *dimA[1] + j“行”,即它指向数组之外。所以循环没有意义。

    函数声明如下

    void printArray(int *A, int* dimA);
    

    被称为

    printArray(A, dimA);
    

    具有int[2] 类型的第二个参数确实被转换为指向数组第一个元素的int * 类型的指针。

    对于第一个参数,它也被转换为指向其第一个元素的指针。数组的元素是什么?这个二维数组的元素是int[2] 类型的一维数组。所以指向这种类型的对象的指针将具有类型int ( * )[2]

    指针int *int ( * )[2] 不兼容,因此编译器应发出诊断消息。

    函数的正确声明应该是这样的

    void printArray(int ( *A )[2], int *dimA);
    

    【讨论】:

      【解决方案2】:

      当尝试将二维数组传递给函数时,我了解到动态分配的数组不能这样做,因为编译器需要知道数组[][列]。

      这是真的,因为你不能将 any 数组传递给函数。你甚至不能用 C 来表达这样的概念,尽管你可以编写看起来像普通人的代码。在几乎所有出现对数组求值的表达式的上下文中(包括函数调用表达式),数组值都被替换为指向第一个数组元素的指针。

      从某种意义上说,二维数组是数组的数组,并且(数组)元素类型的维度是整个数组类型的一部分,每个元素类型的一部分,以及指向第一个元素的指针的类型。因此,该维度必须是您要向其传递的任何函数参数类型的一部分(指向数组第一个元素的指针)。

      然而,即使是二维数组的维度都是在运行时确定的,它最准确的特征是错误的。自 1999 年以来,C 就支持可变长度数组(尽管在 C11 中它是可选的),并且这些数组与动态分配的多维数组和指向不同维数的数组的指针配合得非常好:

      // Dynamically allocating a 2D array of runtime-determined dimensions:
      unsigned rows = calculate_number_of_rows();
      unsigned columns = calculate_number_of_columns();
      int (*matrix)[columns] = malloc(rows * sizeof(*matrix));
      

      它们也适用于接受此类指针的函数:

      void do_something(unsigned rows, unsigned columns, int matrix[rows][columns]);
      

      ...或者,等效地...

      void do_something(unsigned rows, unsigned columns, int matrix[][columns]);
      

      ...或...

      void do_something(unsigned rows, unsigned columns, int (*matrix)[columns]);
      

      这三种形式是完全等价的。

      但是,我了解到一个 2D 数组存储一个 1D 数组,其中每个新行的元素紧跟上一行的元素。

      二维数组是一维数组的数组。任何数组的元素都连续存储在内存中,没有填充,因此二维数组的布局(rc)无法与一维的布局区分开来维度数组 r * c,但我建议不要在术语中考虑它 你用过。

      当我将数组名称作为指向数组的指针传递给函数时,情况似乎就是这样,并且我的代码工作正常。

      不要那样做。在实践中,它很可能完全按照您所说的那样工作,但是您应该注意编译器发出的警告——它肯定应该发出警告。

      但是,在声明二维数组的函数中,它表现为一个指针数组。

      您没有提供符合您描述的函数示例。当然可以传递一个指针数组,但也可以将一个指针传递给一个数组。有关示例,请参见上文。

      【讨论】:

        【解决方案3】:

        编译代码会给出一个警告,这对正在发生的事情有一点线索:

        main.c:27:27: warning: format specifies type 'int' but the argument has type 'int *' [-Wformat]
                    printf("%3d", A[i*dimA[1] + j]);//This would work if the elements of A[] are the rows of a 2D array mapped into a 1D array
                            ~~~   ^~~~~~~~~~~~~~~~
        main.c:32:16: warning: incompatible pointer types passing 'int [2][2]' to parameter of type 'int *' [-Wincompatible-pointer-types]
            printArray(A, dimA);
                       ^
        main.c:3:22: note: passing argument to parameter 'A' here
        void printArray(int *A, int* dimA) {
        

        当你声明你的数组时:

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

        正如您所说,这存储为一个连续的内存块。在内存中,这相当于:

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

        但是,每当您查找/取消引用值时,根据类型,编译器都会隐式地为您做一些记录。对于第二种情况:

        int A[4] = {1,2,3,4};
        
        A[0] = *(&A + 0) = 1
        A[1] = *(&A + 1) = 2
        ...
        

        相当简单,索引只是基地址的偏移量。但是,对于第一种情况:

              y  x
        int A[2][2] = {{1,2},{3,4}};
                             y   x
        A[0][0] = *(&A + 2 * 0 + 0) = *(&A + 0) = 1
        A[1][0] = *(&A + 2 * 1 + 0) = *(&A + 2) = 3
        ...
        

        事情开始有点混乱。

        首先要注意的是,由于类型被声明为int[2][2],您必须取消引用它两次。这就是第一个警告所抱怨的。因为它只被取消引用一次,所以您的int ** 变成了int *,这与int 不同。

        要注意的第二件事是因为类型被声明为多维数组,编译器会为你做一些记录。由于数组在第一个维度上被取消引用,因此已经考虑到跨度到正确位置的第二个维度的大小,因此您实际上得到的是col * j + i,而不是col * (col * j + i) + i,这不是您想要的!

        要获得所需的效果,您可以:

        1. A 转换为int *。这就是您调用 printArray 函数时发生的情况,也是它起作用的原因。

        2. 从最低维度访问数组。与其说A[i*dimA[1] + j],不如说A[0][i*dimA[1] + j]。这将正确地取消引用 int 并有效地绕过簿记。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-23
          • 1970-01-01
          • 1970-01-01
          • 2018-11-20
          • 2016-03-25
          相关资源
          最近更新 更多