【问题标题】:how to Pass 2D array without size into function in C如何将没有大小的二维数组传递给C中的函数
【发布时间】:2017-11-22 13:18:18
【问题描述】:

给我的问题特别指出,函数采用整数 2d 数组和大小作为参数。这意味着, 1) 我不允许在声明时定义数组的大小。 2)我必须将行数和列数传递给函数。

我之前为一维数组做过它并且它工作,但是在二维数组的情况下它给出了一个语法错误。我制作了测试代码来检查语法的逻辑。这是有效的一维数组逻辑。 此外,这个模块只需要我在 C 中使用 stido.h 库。传递数组大小的大小的原因是在不同的函数中填充数组,其中循环条件将使数组大小通过索引

#include<stdio.h>
void print(int table[],int r);
int main()
{   int r=7,table[r];
    print(table,r);
}

 void print(int table[],int r)
{  printf("table rows is%d\n",r);
} 

但是,当我尝试使用 2D 数组时,同样的逻辑不起作用。(给出语法错误)

#include<stdio.h>
void print(int table[][],int c,int r);
int main()
{int c=7,r=7,table[c][r];
    print(table,c,r);
}

void print(int table[][],int c,int r)
{printf("table rows is\ntable cols is\n",r,c)
}

有人可以解释为什么会发生这种情况,并为这个问题提出解决方案。 ? 在此先感谢

【问题讨论】:

  • 您遇到的语法错误是什么?
  • table[][] 是不完整的类型。传递数组时必须至少指定最终维度,或者必须传递指向类型指针的指针(这与数组不同)
  • int** table 传递给您的函数
  • 您的printf() 语句没有格式说明符%d
  • 您可以重新排列参数并尝试使用void print(int c, int r, int table[c][r])...

标签: c arrays function multidimensional-array


【解决方案1】:

您不能有未指定维度的二维数组。但是,您似乎拥有 C99+ 编译器,因此可以使用可变长度数组。但是,您也需要重新排序参数。

请注意,我在此处使用 size_t 而不是 int 作为索引/维度 - 因为可能有一个表(尽管不太可能)包含比 int 表示的更多元素。

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

void print(size_t, size_t, int [*][*]);

int main(void) {
    size_t c = 7, r = 9;
    int table[c][r];
    print(c, r, table);
}

void print(size_t c, size_t r, int table[c][r]) {
    printf("table rows is %zu\ntable cols is %zu\n", r, c);
}

它在 C99 中编译,(以及那些支持可选 VLA 扩展的 C11 编译器);并在运行时打印

table rows is 9
table cols is 7

【讨论】:

    【解决方案2】:

    首先,您想知道为什么这是不可能的。语言标准不允许这样做,技术原因是编译器无法在第二维未知时计算数组的偏移量。

    二维数组就像一维数组一样,内存中的一组值一个接一个。它不是,就像在某些其他语言中一样,是一个数组数组。对于像int a[5][3] 这样的二维数组,您连续有5 次ints,总共有15 个ints。为了访问一个元素,编译器会为你计算偏移量,所以如果你写例如a[2][2],这是 2*3 + 2th 元素(有两行 3 列的第三行开头要跳过)。

    如果编译器不知道第二维,那么这种偏移量的计算是不可能的

    Antti Haapala's answer 已经展示了如何使用 C99 特性可变长度数组来解决这个问题。这是一个很好的方法,但不幸的是,对于 C11,此功能是可选,因此可能有编译器不支持它。如果您确定只使用支持 VLAs 的编译器(这是所有现代 C 编译器中的绝大多数),请使用此答案中的代码。


    当将数组传递给函数时,而不是数组,传递的是指向其第一个元素的指针(数组decays作为指针)。所以下面的声明是一样的:

    void print(size_t n, int arr[]);
    void print(size_t n, int *arr);
    

    有了这些知识,您可能会想用不同的方式编写它并自己进行偏移计算:

    void print(size_t r, size_t c, void *data)
    {
        int *arr = data;
        // access arr[2][2]:
        int v = arr[2*c + 2];
    }
    

    确实,这很有可能会起作用,但请注意这是未定义的int[]int[][] 不是兼容的类型。有关此想法的详细信息,请参阅 this question 并提供两个非常有趣的答案。底线:这种代码很有可能会工作。不过,可以肯定的是,不要这样做....


    因此,如果您没有可变长度数组,并且如果您不想依赖未完全明确定义的东西,那么唯一的另一种方法是实际构建一个 数组数组 通过指针数组:

    int row1[3] = {0, 1, 2};
    int row2[3] = {3, 4, 5};
    int *rows[2] = { row1, row2 };
    

    那么你可以这样传递:

    void print(size_t r, size_t c, int **arr)
    {
        int a = arr[0][2]; // 2
    }
    

    当然,这不再是 2D 数组,它是一种可以以类似方式使用的替代构造。

    【讨论】:

      【解决方案3】:

      如果没有为最终维度定义常量,则无法传递二维数组。 (没有 C99 VLA 添加)。但是,您可以更改print 中参数的顺序,以便在将table 声明为指向int [r] 数组的指针之前定义r,并将table 传递为跟随,例如

      #include<stdio.h>
      
      void print (int c, int r, int (*table)[r]);
      
      int main (void) {
      
          int c = 7, r = 7, table[c][r];
      
          for (int i = 0; i < r; i++)
              for (int j = 0; j < c; j++)
                  table[i][j] = i * j + i + j;
      
          print (c, r, table);
      
          return 0;
      }
      
      void print (int c, int r, int (*table)[r])
      {
          for (int i = 0; i < r; i++) {
              for (int j = 0; j < c; j++)
                  printf (" %3d", table[i][j]);
              putchar ('\n');
          }
      }
      

      此方法适用于使用带有 C99 VLA 扩展的 VLA。

      使用/输出示例

      $ ./bin/fpass2d
         0   1   2   3   4   5   6
         1   3   5   7   9  11  13
         2   5   8  11  14  17  20
         3   7  11  15  19  23  27
         4   9  14  19  24  29  34
         5  11  17  23  29  35  41
         6  13  20  27  34  41  48
      

      如果 VLA 扩展不可用,您可以通过分配二维数组然后手动将其索引为一维数组来解决此问题。这是因为二维数组是按顺序存储在内存中的数组数组(例如a[2][3] 在内存中是a00,a01,a02,a10,a11,a12。)为了防止任何未定义的行为,您可以为table 分配存储空间(例如int *p = malloc (sizeof table); 复制table 然后索引每个调用print (p, c, r) 的元素,如下所示,例如

      void print (int *table, int c, int r)
      {
          for (int i = 0; i < r; i++) {
              for (int j = 0; j < c; j++)
                  printf (" %3d", table[i * r + j]);
              putchar ('\n');
          }
      }
      

      其中table 使用table[i * r + j] 进行索引,以访问二维数组的每个原始元素。虽然此方法是一种解决方法,但如果没有 VLA 扩展,您可能会为最终的数组维度传递一个已定义的常量,从而使解决方法或更改参数顺序变得不必要。

      检查一下,如果您有任何问题,请告诉我。

      【讨论】:

      • 由于不兼容的类型和/或读取了 first int[7] 数组的最后一个索引,导致行为未定义。我会谨慎对待它:D
      • 嗯好奇,标准明智,严格别名明智,你看到了什么?我问是因为main 中的table[0]int *,所以*p = *table 的分配是正确的。如果您传递尺寸cr,我看不到UB? (不是说你错了,是说我没看到)
      • @AnttiHaapala 我明白你在说什么。如果它不是一个数组,在内存中是连续的,那么它就会跑到 UB 中。明白了。使用数组本身,没有 UB 结果。
      • 我认为@AnttiHaapala 提出的观点是p 指向一个包含7 个元素的数组对象,并且尝试访问这7 个元素之外的都是UB。此外,您可能对this example of UB from Annex J 感兴趣:“数组下标超出范围,即使对象显然可以使用给定的下标访问(如在左值表达式 a[1][7] 中给出声明 int a[ 4][5]) (6.5.6)。”或this question and related discussion.
      • @DavidBowling 是的,我确实看到了,一旦灯泡亮起...为了避免 UB,您需要创建并传递一个带有 table 副本的分配对象(例如int *p = malloc (sizeof table); memcpy (p, table, sizeof table);。然后如上所示通过p进行打印将不受UB。(我会尽快更新答案)
      【解决方案4】:

      首先,函数参数的类型衰减为指向数组第一个元素的指针。也就是说,下面的声明对是完全等价的:

      void f(int a[]);
      void f(int *a);
      
      void g(int a[][7]);
      void g(int (*a)[7]);
      
      void h(int a[][7][6]);
      void h(int (*a)[7][6]);
      

      现在,当您对数组进行索引时,该操作实际上是根据指针算术定义的。所以,a[3] 这个表达式实际上等价于*(a + 3)

      在这个表达式中,a 产生一个指向数组第一个元素的指针,并且添加偏移量以跳过数组的三个元素。也就是机器会执行这个计算:*(elementType*)((char*)a + 3*sizeof(elementType))

      这其中的关键部分是sizeof(elementType):如果a的类型是int[][],那么它的元素类型是int[],你不能取一个未知长度的数组的大小明显的原因。因此,必须知道除最外层之外的所有数组维度的类型,以便能够对多维数组进行索引。


      对于正在发生的事情的解释就这么多。对你来说结果是,你必须提供内部维度的大小。正如其他答案所示,这可以通过简单地首先排序大小参数来完成,因此它们可用于数组参数的声明:

      void print(int c, int r, int table[c][r]);
      

      或者,等效地,c 的值实际上在上面的声明中被忽略了:

      void print(int rowCount, int columnCount, int (*table)[columnCount]) {
          printf("table has %d columns and %d rows\n", columnCount, rowCount);
      
          for(int curRow = 0; curRow < rowCount; curRow++) {
              for(int curColumn = 0; curColumn < columnCount; curColumn++) {
                  printf("%d ", table[curRow][curColumn]);
              }
              printf("\n");
          }
      }
      

      (在最后一个示例中,我冒昧地使用了正确的格式和变量名称。请不要只使用单个字符来表示变量名称,因为变量名称与数组维度大小一样重要。)

      【讨论】:

        猜你喜欢
        • 2017-07-26
        • 2011-05-24
        • 2012-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-30
        • 2021-01-11
        • 2014-12-12
        相关资源
        最近更新 更多