将一个二维数组传递给一个常量参数的函数

7

我从《C Primer Plus》中了解到,如果你想保护一个数组不被函数意外修改,应该在函数定义的头部,在指针声明之前添加const修饰符。

按照这个明智的建议,在下面这个最小的示例中,我试图将非常量二维数组array传递给函数Sum2D,其中一个参数是pointer-to-const-int[2]

#include <stdio.h>
#define ROWS 2
#define COLS 2
int Sum2D(const int ar[][COLS], int rows); //use `const` to protect input array
int main(void)
{
    int array[ROWS][COLS]={{1,2},{3,4}}; //the non-constant array

    printf( "%d\n", Sum2D(array,ROWS) );

    return 0;
}

int Sum2D(const int ar[][COLS], int rows)
{
    int total=0;
    int i,j;
    for( i=0 ; i<rows ; i++ )
    {
        for( j=0 ; j<COLS ; j++ )
        {
            total+=ar[i][j];
        }
    }
    return total;
}

然而,gcc 编译这段代码时会发出以下警告信息:
$gcc -ggdb3 -Wall -Wextra -o test test.c

test.c: In function ‘main’:
test.c:16:2: warning: passing argument 1 ofSum2D’ from incompatible pointer type [enabled by default]
  printf( "%d\n", Sum2D(array,4) );
  ^
test.c:4:5: note: expected ‘const int (*)[4]’ but argument is of type ‘int (*)[4]’
 int Sum2D(const int ar[][COLS], int rows);
     ^

1) 警告是什么意思?

2) 除了在 array 声明中添加 const,如何消除 "噪音"?(如果 array 和函数都使用一维数组,则不会有警告。)

系统信息:

Ubuntu 14.04LTS

编译器:gcc 4.8.2


@inneedofhelp 实际上,该函数期望一个指向大小为COLS的const int数组的指针。在函数参数中,const int ar[][COLS]const int (*ar)[COLS]是相同的。 - juanchopanza
你为什么要使用参数“rows”,而不是直接使用定义的维度? - Mario
@inneedofhelp 但这实际上是《C Primer Plus》书中的一个简化示例。它说将非const指针分配给const指针是可以的,因为“const”是为了保护。也许这不正确? - Naitree
@Mario 不是的,这只是用于具有不同行数的数组。 - Naitree
1
编译器允许编译不符合规范的程序作为扩展,因此这不会是一个严重的错误,但如果有人以这种方式编写代码并将代码移植到没有该扩展的编译器中,则可能会很烦人。 - M.M
显示剩余6条评论
2个回答

13
这是C语言设计上的一个不幸的“漏洞”:T (*p)[N]并不能自动转换为T const (*p)[N]。你要么使用丑陋的强制类型转换,要么让函数参数不接受const
乍一看,似乎应该允许这种转换。在C11 6.3.2.3/2中规定:
对于任何限定符q,指向非q限定版本的类型的指针可以转换为该类型的q限定版本的指针;
然而,再看看C11 6.7.3/9(在C99中为/8):
如果数组类型的规范包括任何类型限定符,则元素类型是带有限定符的,而不是数组类型。
这最后一句话说,int const[4]并不被认为是int[4]const限定版本。实际上它是一个非const限定的数组,其中有4个const intint[4]int const[4]是不同元素类型的数组。
因此,6.3.2.3/2实际上不允许将int (*)[4]转换为int const (*)[4]
另一个奇怪的情况是,在使用typedef时,这个与const和数组有关的问题会出现。例如:
typedef int X[5];
void func1( X const x );
void func1( int const x[5] );

这会导致编译错误:X const x表示x是const,但它指向一个非const的int数组;而int const x[5]表示x不是const,但它指向一个const的int数组!
更多阅读请点击此处,感谢@JensGustedt。

1
你有这方面的参考资料吗?我想知道这是否意味着clang正在将其作为扩展进行“修复”。我尝试在严格模式下编译(-Wall -Wextra -Wconversion -pedantic-errors -std=cxx,其中xx是89、99、11),看看能否解决问题。 - juanchopanza
我认为我的知识还太有限,无法完全理解您的意思。但是当我尝试时,我想问一下:const int (*)[4]int const (*)[4]是同一件事吗?因为前者是我在警告信息中看到的。 - Naitree
1
@Naitree const int (*)[4]int const (*)[4] 是同一件事情;都是指向四个 const int 数组的指针。两者int (*const)[4] 不同,后者是指向四个非 const int 的 const 指针数组。如果还有遗漏,那就是 int const (*const)[4],一个指向四个 const int 数组的 const 指针。 - WhozCraig
1
在 c23 的最新草案中,文本已更改为“如果数组类型的规范包括任何类型限定符,则数组和元素类型都被限定。”。因此,这个问题可能终于得到解决。 - tstanisl
@tstanisl 很高兴听到这个消息,尽管我怀疑并不是每个人都会迅速升级到 C23 :) - M.M
显示剩余4条评论

0

在调用函数时,您可以对数组进行类型转换。它不会自动将非const转换为const。您可以使用这个。

Sum2D( (const int (*)[])array, ROWS );

我实际上仍然会收到警告,即使使用了您的类型转换。我想您的意思是(const int (*)[])。然后警告就消失了。 - Naitree

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