如何将二维数组传递给函数?

3

这段代码能够正常运行,它会输出数组中的所有整数,但是会产生一个警告。我想知道如何正确地做并避免警告。

#include <stdio.h>
#include <stddef.h>

void print_all(int * array, ptrdiff_t num_rows, ptrdiff_t num_cols) {
    for (ptrdiff_t i = 0; i < num_rows; ++i) {
        for (ptrdiff_t j = 0; j < num_cols; ++j) {
            printf("%d\n", array[num_cols * i + j]);
        }
    }
}


int main() {
    int array[2][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8}
    };
    print_all(array, 2, 4);
}

这是警告信息:
$ clang -Wall -Wextra -pedantic -fsanitize=address -std=c11 arrays.c
arrays.c:18:15: warning: incompatible pointer types passing 'int [2][4]' to
      parameter of type 'int *' [-Wincompatible-pointer-types]
    print_all(array, 2, 4);
              ^~~~~
arrays.c:4:22: note: passing argument to parameter 'array' here
void print_all(int * array, ptrdiff_t num_rows, ptrdiff_t num_cols) {
                     ^
1 warning generated.

print_all函数仅接受具有某些指定列数的数组是不可接受的。


如果你想传递一个二维数组,为什么不直接传递一个指向一维数组的指针呢? - too honest for this site
你正在利用数组元素在内存中的连续性,但是你的函数期望一个 int *,而你正在传递一个 int (*)[4]。使用正确类型的指针调用函数 print_all(array[0], 2, 4);,以消除警告。你确定这是你想要保留的代码设计吗? - Bob__
4个回答

4
如果你的编译器支持可变长度数组(VLA),那么你可以这样做。
void print_all(ptrdiff_t num_rows, ptrdiff_t num_cols, int array[num_rows][num_cols]) {
    for (ptrdiff_t i = 0; i < num_rows; ++i) {
        for (ptrdiff_t j = 0; j < num_cols; ++j) {
            printf("%d\n", array[i][j]);
        }
    }
}


int main(void) {
    int array[2][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8}
    };
    print_all(2, 4, array);
}

"

num_rowsnum_cols参数必须在array参数之前,以便编译器知道数组的维度。VLA允许您使用2D数组索引array[i][j],而不是自己计算。

如果您不能使用VLA或不想使用VLA,则解决方案就是传递2D数组第一个元素的地址。

"
print_all(&array[0][0], 2, 4);

@artm 第6.5.6/9节:“当两个指针相减时,它们都应该指向同一个数组对象的元素或者是数组对象中的最后一个元素之后;结果是两个数组元素下标的差。结果的大小是由实现定义的,并且其类型(有符号整数类型)在<stddef.h>头文件中定义为ptrdiff_t。如果结果无法在该类型的对象中表示,则行为未定义。” - user3386109
你的意思是我们应该在这段代码中使用 ptrdiff_t 吗?我没有看到任何指针减法操作啊? - artm
@artm 我的意思是OP在使用ptrdiff_t作为num_rowsnum_cols类型时是正确的。由于两个数组索引相减的结果必须适合于ptrdiff_t,因此可以得出结论,数组索引本身也必须适合于ptrdiff_t - user3386109
1
@user3386109:不是的。size_t是保证能够容纳数组长度的类型,因此它是正确的类型。它是由sizeof返回的类型,并用于malloc/calloc等函数。 - too honest for this site
@artm:对于gcc <5,您至少必须指定-std=c99(或c11/gnu *)。 - too honest for this site

2

一个二维数组类似于int[rows][cols]。那么为什么不使用同样的语法来表示函数参数呢?

void print_all(size_t rows, size_t cols, int array[rows][cols]);

请注意使用正确的类型来表示数组的维度,不要使用有符号的ptrdiff_t,而是使用size_t。此外,您还需要先声明维度参数,以便在数组声明时已知。
与大多数数组用法一样,数组的名称会隐式转换为指向第一个元素的指针,它本身是一个一维数组:int (*)[cols]
对于元素array[r][c],请使用普通的二维索引运算符。
在数组的参数声明中,外层(rows)长度实际上不是必需的,可以省略:int array [][cols]。但是,出于文档目的,最好还是将其指定。
整个过程与常量长度数组非常相似。它被称为可变长度数组(VLA,由于显而易见的原因),自C99以来成为标准C语言(虽然C11将其设置为可选项,但任何现代编译器都应该支持它)。
如果您使用古老的编译器,请使用下面的宏定义常量长度数组。
附注: 不要使用魔术数字。在main函数中定义数组的维度并仅使用宏定义。
#define ROWS 2
#define COLS 4

...

int main(void)
{
    int array[ROWS][COLS] = ...;

    print_all(ROWS, COLS, array);
}

我还使用了正确的main签名; 空参数列表已被弃用,可能会在标准的未来版本中被删除。

这样,您只需更改宏即可更改维度的长度。


num_rowsrows - 它们不应该是相同的标识符吗?列也是同样的问题。 - CrabMan
@CrabMan:看到编辑内容了(复制/粘贴错误)。我使用了更短的名称。 - too honest for this site
哇,它真的可以工作。这是什么神奇的魔法?我不认为我听说过你可以做这个array[rows][cols]的事情,其中rowscols是其他参数。 - CrabMan
@CrabMan:这基本上与具有常量长度的数组相同,其中您可以使用#define ROWS 10等。正如我所写的那样,您只需在使用它们之前指定用于维度的变量即可。编译器将使用[]中的表达式来计算长度。我会添加一条注释。 - too honest for this site

1
你的print_all()只接受一维数组,但是你的main正在解析二维数组,因此出现了警告。
你可以保持函数不变,将你的array更改为一维数组,它应该可以工作。
void print_all(int * array, ptrdiff_t num_rows, ptrdiff_t num_cols) {
    for (ptrdiff_t i = 0; i < num_rows; ++i) {
        for (ptrdiff_t j = 0; j < num_cols; ++j) {
            printf("%d\n", array[num_cols * i + j]);
        }
    }
}

main() 中。
int array[] = {1, 2, 3, 4, 5, 6, 7, 8};

回复内容存在敏感词^**$中使用2D数组,则需要更改print_all()函数。
void print_all(int array[][4], ptrdiff_t num_rows) {
    for (ptrdiff_t i = 0; i < num_rows; ++i) {
        for (ptrdiff_t j = 0; j < 4; ++j) {
            printf("%d\n", array[i][j]);
        }
    }
}

并且在main函数中

int array[][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8}
};

您需要提供第二个维度(4),但可以省略第一个维度(2);这个二维数组在内存中被存储为一系列的一维数组。


OP没有使用魔数,那你为什么要修改呢?魔数是一种非常糟糕的做法。 - too honest for this site

-3
指针就是指针。只要你知道如何在函数中解释它,你就可以忽略/抑制警告。 当然,最好的做法是在两端使用匹配类型。

1
那是危险的胡说八道! - too honest for this site
说某些东西是“危险的胡说八道”而没有具体说明是无意义的。它 a) 不比仅使用指针更危险 b) 在高质量代码中经常使用。我们谈论的是 C,而不是带有所有强制转换运算符和智能指针等的 C++。 - greg
如果你真的必须问“为什么”,那么你应该阅读一本C语言书籍并学习编程。专注于静态类型语言的思想。提示:正确的代码、类型检查、编译器是你的朋友,而不是敌人。 - too honest for this site
那么,你认为应该如何完成呢?抱歉,我有点过时了,只学了20年的C语言。 - greg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接