c-为什么gcc和clang不警告写入地址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()的参数,但它们都没有警告如何写入地址0或空指针。即使指定了-O3,也可以确保isEven()函数是内联的,而且我还指定-Wall -Wextra

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

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

毫无疑问,让编译器检测它会很有用,但可能不可能在所有情况下都检测到。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文档:

-Wnull取消引用
如果编译器检测到由于取消引用null指针而触发错误或未定义行为的路径,则发出警告。只有当-fdelete null指针检查为活动,这是由大多数目标中的优化启用的。这个警告的精度取决于所使用的优化选项。

这只是因为编译器不够聪明。可以让它们变得如此聪明,但最有可能的是,编译器构造函数认为这不值得付出努力。因为归根结底,编写正确的代码是程序员的责任。

事实上,他们非常聪明。正如OznOg在下面的评论中提到的,优化器正在使用这样的东西来加快代码。但该标准并不要求编译器对此发出警告,而且C有一种非常关注程序员的心态。它根本不像Java那样握着你的手。

此外,这种警告很容易产生大量误报。在您的代码中,这是相当琐碎的,但可以很容易地想象出具有分支的更复杂的示例。像这个代码:

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

在最后一行,如果n是奇数,我们将写入NULL;如果n是偶数,则写入q。并且q是有效地址。好吧,前提是malloc成功了。这是否也应该产生警告?

gcc有一个编译器选项,可能还有其他编译器选项。但是,这不是标准所要求的。默认情况下不启用的原因与标准不要求的原因基本相同

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

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 8.4也没有诊断出main()中该指针的取消引用,即使有标志-Wall -Wextra -pedantic也是如此。

应该吗?没有要求它这样做。行为是未定义的,但它不是违反约束的,所以C不需要实现来诊断它。如果编译器确实诊断出了这一点——也许有些编译器也诊断出了——这肯定会很有帮助,但归根结底是实现质量问题。

最新更新