简体   繁体   中英

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

The following erroneous code:

#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);
}  

was recently posted here on SO in a question. The question itself doesn't matter, what does matter (to me) is that while gcc and clang warn about the use of isFlag as an argument to printf() , neither of them is warning about how address 0 or a null pointer is written to. This, even when -O3 is specified, ensuring the isEven() function is inlined, and when I also specify -Wall -Wextra .

Should there not be a warning in this case?

Dereferencing a null pointer is undefined behaviour. There's no requirement to issue diagnostics (errors or warnings) for undefined behaviour. So from the standard point of view, it's totally fine to not produce any warnings for your example.

Undoubtedly, it'd be useful to have compilers detect it, it may not be possible to detect in all cases. gcc does have -Wnull-dereference which does detect for your example and produces:

$ 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;
      |         ~~~~~~~~^~~

From 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.

It's simply because the compilers are not smart enough to do it. It would be possible to make them so smart, but most likely, the compiler constructors simply does not think it's worth the effort. Because in the end, writing correct code is the programmers responsibility.

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.

Furthermore, warnings of that kind would easily generate a lot of false positives. In your code it's fairly trivial, but one can easily imagine more complex examples with branches. Like this code:

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

On the last line we will write to NULL if n is odd and to q if n is even. And q is a valid address. Well, provided that malloc succeeded. Should this also generate a warning?

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.

And in this situation, the correct way of handling it would be that YOU add a null check in the function. Like this:

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 compilers usually don't remember values. For instance, this generates the warning warning: division by zero :

int y = 1/0;

but not this:

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

One way to detect these errors is to compile with --fsanitize=address . You will not discover it during compile time, but during runtime.

Should there not be a warning in this case?

I was about to say something about separate compilation, etc ., but I find that GCC 8.4 does not diagnose a dereference of that pointer in main() , either, even with flags -Wall -Wextra -pedantic .

Should it? There is no requirement for it to do so. The behavior is undefined, but it is not a constraint violation, so C does not require implementations to diagnose it. It sure would be helpful if compilers did diagnose this -- and perhaps some do -- but it comes down to a quality of implementation issue.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM