【问题标题】:About warning: "note: expected 'const int **' but argument is of type 'int **'"关于警告:“注意:预期 'const int **' 但参数的类型为 'int **'”
【发布时间】:2019-08-09 05:18:01
【问题描述】:

我用 C 编写“将动态 2D 传递给函数”。

以下代码编译成功,运行良好。

void iter_2d(const int** arr, 
             const size_t row, 
             const size_t column) {
// ..some code
}

int main(){
  const size_t column = 3, row = 4;
  int** arr = (int**)malloc(sizeof(int*) * row);
  for (size_t i = 0; i < row; i++)
    arr[i] = (int*)malloc(sizeof(int) * column);
  // init val for array
  iter_2d(arr,row,column);
  // clear array

}

但我收到警告:

t.c:24:11: warning: passing argument 1 of 'iter_2d' from incompatible pointer type [-Wincompatible-pointer-types]
   iter_2d(arr,row,column);
           ^~~
t.c:4:26: note: expected 'const int **' but argument is of type 'int **'
 void iter_2d(const int** arr,
              ~~~~~~~~~~~~^~~

我认为函数 iter_2d 只是迭代数组的值,不能在函数 iter_2d 中修改,

所以输入参数arr应该是const to pointer。

但是编译器告诉我这个警告让我很困惑。

【问题讨论】:

  • 删除所有consts。
  • 制作指向指针的指针是制作二维数组的不好方法,因为它浪费空间和时间。如果您只能针对支持可变长度数组的 C 实现,那么只需执行 int (*arr)[column] = malloc(row * sizeof *arr);,并将函数声明更改为 void iter_2d(size_t row, size_t column, const int (*arr)[column])。如果您必须针对可能不支持可变长度数组的 C 实现,那么您可以使用int 的一维数组并编写自己的索引算法(二维r 和c 的平下标是@ 987654330@).
  • 此外,计算机供应商不再对空格字符收费,因此您可以将iter_2d(arr,row,column); 更改为iter_2d(arr, row, column); 并进行其他更改以提高可读性。
  • 我们需要一个规范的骗子来解决这个问题,这很常见。
  • 与你的问题无关,你可能想看看Correctly allocating multi-dimensional arrays。

标签: c


【解决方案1】:

从 char ** 到 const char ** 的转换违反约束的原因在 C 2018 标准 6.5.16.1 6 中的示例 3 中给出。假设我们有:

const char **cpp;
char *p;
const char c = 'A';

接下来,考虑&amp;p。这是char **。如果我们允许转换为const char **,那么我们可以将其分配给cpp:

cpp = &p; // Violation of C constraints for assignment.

假设我们这样做了。那么cpp 是const char **,所以*cpp 是const char *。这意味着我们可以为它分配const char 的地址,如下所示:

*cpp = &c;

现在*cpp 是指向c 的指针。因为cpp指向p,所以*cpp就是p,也就是说p指向c。所以现在我们可以这样做了:

*p = 0;

这改变了c,但c 是const char,我们不应该能够改变它。

因此,允许从 char ** 到 const char ** 的转换违反了常量对象的预期行为。

此示例使用赋值表达式,但将参数传递给函数被定义为类似于将参数分配给参数。约束是一样的。

【讨论】:

  • 我不认为示例是 ISO 标准中的规范性文本。约束宁愿是 6.5.16.1/1,关于兼容类型的第三个项目符号。
  • @Lundin:这个答案是为了解释为什么规则是这样的,而不是解释问题中的代码如何违反规则。
【解决方案2】:

当将参数传递给函数时,它们被“像通过赋值一样”复制,这意味着参数复制遵循与= 运算符相同的规则,正式称为简单赋值。因此,您的代码实际上在两行之间执行的操作与此基本相同:

int** arr1 = ... ;
const int** arr2 = arr1;

如果您尝试编译该 sn-p,您将收到几乎相同的错误消息,例如“从不兼容类型初始化”。


做简单赋值时,复制指针的规则是(简化):

  • (= 运算符的)左操作数可以具有限定或非限定指针类型。限定含义,例如const。
  • 两个操作数都必须是指向兼容类型的限定或非限定版本的指针。
  • left 指向的类型必须至少具有right 指向的类型的所有限定符。 (意思是int x; const int y = x; 可以,但反过来不行。)

对于您拥有int* x; const int* y = x; 的情况,编译器不会抱怨。 y 是指向 int 类型的限定指针,x 是指向 int 类型的非限定指针。 y 至少具有 x 的所有限定符。上面提到的所有规则都满足了,所以这很好。

这里的问题是限定符如何与指针对指针一起工作。 const int** 实际上意味着(从右到左阅读)“指向 const int 的指针”。它确实 not 表示“指向 int 指针的 const 指针”。

如果我们回到const int** arr2 = arr1; 的第一个例子,arr2 是一个指向const int* 类型的非限定指针。 arr1 是一个指向int* 类型的非限定指针。它们不是兼容的类型——arr2 碰巧指向一个类型是arr1 指向的限定版本并不重要。规则只关心“最外层”指针类型本身。

要解决这个问题并保持“const 正确性”,我们必须对指针指向指针本身进行 const 限定。那将是int**const arr。再一次,从右到左阅读:“const pointer to pointer to int”。


然而,我的建议是完全摆脱指向指针的指针,因为您的代码因此而变得不必要地缓慢。多次 malloc 调用将导致碎片分配和数据缓存利用率低下。相反,您可以像这样使用指向 VLA 的指针:

#include <stdlib.h>

void iter_2d (size_t row, 
              size_t column,
              int arr[row][column]);

int main (void){
  const size_t row = 4;  
  const size_t column = 3;
  int (*arr)[column] = malloc( sizeof(int[row][column]) );

  iter_2d(row, column, arr);

  free(arr);
}

此代码更快、更简单、更易于阅读。
更多信息:Correctly allocating multi-dimensional arrays。

【讨论】:

  • 感谢您为本示例提供了良好的编码风格。
  • 但是这个语法不能用 cl.exe (Microsoft) 编译。 gcc和clang可以正常编译。
  • @curlywei 您将需要一个不超过 20 年的符合标准的 C 编译器。我不确定微软是否有。
猜你喜欢
  • 1970-01-01
  • 2017-06-14
  • 2013-12-16
  • 2021-12-12
  • 1970-01-01
  • 1970-01-01
  • 2021-11-06
  • 2021-07-09
  • 2011-06-12
相关资源
最近更新 更多