为什么指针的隐式指针到指针的转换是合法的?

Why is implicit pointer of pointer to pointer conversion legal?

我最近在 Whosebug 上看到一些代码,其中指向用于更改分配内存的指针的指针。在检查代码时,我错误地将一个符号添加到一个指针,因此使一个指向指针的指针仍然编译器愉快地编译并且发生运行时错误。举个例子

#include <stdio.h>

void func(int **p) {
  printf("Pointer is at %p\n", p);
}

int main(void) {
  int *p = 0;
  func(p);
  func(&p);
  int **pp = &p;
  func(&pp);
  return 0;
}

我确实知道 C 对指针的限制明显低于 C++,并且允许类似 char *buf = malloc(SIZE) 的东西,而在 C++ 中是不允许的。我认为这是一种便利,因为它在 C 语言中经常发生。

尽管如此,我认为引用的数量是一个很大的错误来源,我想知道为什么有人会允许这样做,尤其是因为 intint* 不同。此外,我想知道 C 标准是否对此有所说明。

编辑 我在 ideone.com 下编译它可能不会显示警告。我本地的 clang 编译器和 gcc 都会发出警告。不过,为什么当它们代表不同的东西时只是警告。


PS,感觉SO最近六年应该问过这样的问题。如果这是重复的,很抱歉找不到。

编译器显然没有启用警告:

int main()
{
    int *p = 0, **p2 = p;

...

$ gcc -std=c11 test.c -lncurses
test.c: In function ‘main’:
test.c:8:21: warning: initialization from incompatible pointer type [enabled by default]
  int *p = 0, **p2 = p;
                     ^

所以,gcc 抱怨得很好。该站点可能已禁用警告或吞下它。它应该两者都不做。

对于 gcc,您还可以使用 -Werror 或某些特定的 -Werror=<name of warning> 将所有警告转换为错误。为此,它将是 strict-aliasing.

之所以不是默认的,应该是历史原因。默认情况下将其设置为错误可能会破坏太多遗留软件或在野外破坏软件。

可能会找到它不产生错误的原因here in the standard。句子 7. 如果指针未针对接收指针正确对齐或取消引用(此处未完成),则仅是 UB。

我认为 ideone 只是对警告软弱。如果我的计算机上没有 -Wall 或任何额外的警告标志,gcc 会给我

test_ptrs.c: In function ‘func’:
test_ptrs.c:4:3: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int **’ [-Wformat=]
   printf("Pointer is at %x\n", p);
   ^
test_ptrs.c: In function ‘main’:
test_ptrs.c:9:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(p);
   ^
test_ptrs.c:3:6: note: expected ‘int **’ but argument is of type ‘int *’
 void func(int **p) {
      ^
test_ptrs.c:12:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(&pp);
   ^
test_ptrs.c:3:6: note: expected ‘int **’ but argument is of type ‘int ***’
 void func(int **p) {

使用错误的说明符打印数据类型调用未定义的行为。将 %x 更改为 %p 并将 printf 的参数转换为 void * ,您将收到警告。

Live Demo

转换合法。更准确地说,没有从 int*int** 或从 int***int** 的隐式转换。试图将 int*int*** 传递给需要 int** 参数的函数是 约束违规 ;任何符合标准的编译器都必须对其进行诊断。 (诊断消息可能是非致命警告。)

当我用 gcc 编译你的代码时,即使使用默认选项(这使得 gcc 不符合要求),我也会收到几个警告:

c.c: In function ‘func’:
c.c:4:3: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int **’ [-Wformat=]
   printf("Pointer is at %x\n", p);
   ^
c.c: In function ‘main’:
c.c:9:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(p);
   ^
c.c:3:6: note: expected ‘int **’ but argument is of type ‘int *’
 void func(int **p) {
      ^
c.c:12:3: warning: passing argument 1 of ‘func’ from incompatible pointer type [enabled by default]
   func(&pp);
   ^
c.c:3:6: note: expected ‘int **’ but argument is of type ‘int ***’
 void func(int **p) {
      ^

不知道ideone为什么不吐槽(http://ideone.com/uzeXur)