为什么 gcc 和 clang 不警告写入地址 0?

Why do gcc and clang not warn about writing to address 0?

以下错误代码:

#include <stdio.h>
#include <string.h>

void isEven (int *isFlag, int num) {
    if (num % 2 == 0) {
        *isFlag = 1;
    } else {
        *isFlag = 0;
    }
}

int main() {
    int num = 4;
    int *isFlag = 0;
    isEven(isFlag, num);
    printf("%d", isFlag);
}  

最近在 SO 上发布了一个问题。问题本身并不重要,(对我而言)重要的是,虽然 gcc 和 clang 警告使用 isFlag 作为 printf() 的参数,neither of them 警告如何地址 0 或空指针被写入。这一点,即使在指定 -O3 时,确保 isEven() 函数是内联的,并且当我还指定 -Wall -Wextra.

这种情况下不应该有警告吗?

这仅仅是因为编译器不够聪明,无法做到这一点。让它们变得如此聪明是可能的,但最有可能的是,编译器构造函数根本认为不值得付出努力。因为最终,编写正确的代码是程序员的责任。

In fact, they are so smart. As OznOg mentioned below in comments, the optimizer is using things like this to make the code faster. But the standard does not require a compiler to issue a warning for this, and C has a mentality that has a very strong focus on the programmer. It simply does not hold your hand like Java does.

此外,这种警告很容易产生大量误报。在您的代码中,它相当微不足道,但可以很容易地想象出带有分支的更复杂的示例。喜欢这个代码:

int *q = malloc(*q);
int *p = 0;
if(n%2 == 0) p = q;
*p = 42;

在最后一行,如果 n 是奇数,我们将写入 NULL,如果 n 是偶数,则写入 qq 是一个有效地址。嗯,前提是malloc成功了。这也应该产生警告吗?

There is a compiler option for gcc and possibly others that gives you this. However, it's not required by the standard. And the reason for not being enabled by default is basically the same as to why the standard does not require it.

在这种情况下,正确的处理方式是在函数中添加空检查。像这样:

void isEven (int *isFlag, int num) {
    if(!isFlag) {
         /* Handle error. Maybe error message and/or exit */
    } else if (num % 2 == 0) {
        *isFlag = 1;
    } else {
        *isFlag = 0;
    }
}

C 编译器通常不记住值。例如,这会生成警告 warning: division by zero:

int y = 1/0;

但不是这个:

int x = 0;
int y = 1/x;

检测这些错误的一种方法是使用 --fsanitize=address 进行编译。您不会在编译时发现它,而是在运行时发现它。

取消引用空指针是未定义的行为。不需要为未定义的行为发出诊断(错误或警告)。因此,从标准的角度来看,不为您的示例产生任何警告是完全可以的。

毫无疑问,让编译器检测它会很有用,它可能无法在所有情况下检测到。 gcc 确实有 -Wnull-dereference 确实为您的示例检测并生成:

$ gcc -O3 -Wall -Wextra -Wnull-dereference -fsanitize=address test.c
test.c: In function ‘main’:
test.c:14:14: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
   14 |     printf("%d", isFlag);
      |             ~^   ~~~~~~
      |              |   |
      |              int int *
      |             %ls
test.c:4:17: warning: null pointer dereference [-Wnull-dereference]
    4 |         *isFlag = 1;
      |         ~~~~~~~~^~~

来自gcc documentation

-Wnull-dereference
Warn if the compiler detects paths that trigger erroneous or undefined behavior due to dereferencing a null pointer. This option is only active when -fdelete-null-pointer-checks is active, which is enabled by optimizations in most targets. The precision of the warnings depends on the optimization options used.

Should there not be a warning in this case?

我正要说一些关于单独编译的事情,,但我发现 GCC 8.4 也没有诊断出 main() 中该指针的取消引用, 即使有标志 -Wall -Wextra -pedantic.

应该吧?没有要求它这样做。该行为是未定义的,但它不是约束违规,因此 C 不需要实现来诊断它。如果编译器确实对此进行了诊断,那肯定会有所帮助——也许有些人会这样做——但这归结为实施质量问题。