4

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?

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 2
    `NULL` (i.e. `(void *)0`) may not be exactly the `int` representation of `0`. – Govind Parmar Mar 26 '21 at 19:06
  • 3
    Why should it? it does not follow calls to functions to see how the argument is used. And the compiler is not obliged to see if there is a test of `isFlag`'s value in the function. And suppose the function were in another compilation unit, so the logic *can't* be easily checked. – Weather Vane Mar 26 '21 at 19:10
  • @GovindParmar That's true, but `ptr = 0` is guaranteed to initialize `ptr` to `NULL` – klutt Mar 26 '21 at 19:10
  • 3
    On some processors, address `0` is valid. – Fiddling Bits Mar 26 '21 at 19:19
  • @FiddlingBits `ptr = 0` is valid code on ALL platforms according to the standard – klutt Mar 26 '21 at 19:21
  • 1
    @klutt You're right. What I meant to say was there may be actual memory (e.g Flash, RAM) at that address. – Fiddling Bits Mar 26 '21 at 19:22
  • int *isFlag = 0; is totally valid, it just means that you create a NULL ptr; hence why the compiler accepts it – Antonin GAVREL Mar 26 '21 at 19:23
  • Address 0 is relative address of program and architecture has freedom to set some heap memory at that address. In that case compiler warning will rather be a bug then a facility. – Avezan Mar 26 '21 at 19:33
  • @FiddlingBits: Maybe, but not on the default targets on my machine, or on GodBolt. Also, see what klutt said. – einpoklum Mar 26 '21 at 19:44
  • @AntoninGAVREL: If it were invalid, it would be an error. I asked why there isn't a warning. – einpoklum Mar 26 '21 at 19:45
  • The value of `isFlag` is definitely a null pointer value, regardless of how a null pointer is represented. The assignment to `*isFlag` definitely has undefined behavior. – Keith Thompson Mar 26 '21 at 20:07
  • @einpoklum I edited the comment to remove the stuff about `printf`; I hadn't noticed that was in the question. I've retained what I wrote about `isFlag` being a null pointer, in response to other comments that seem to imply that the representation of a null pointer is relevant. – Keith Thompson Mar 26 '21 at 20:11

3 Answers3

4

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.

P.P
  • 117,907
  • 20
  • 175
  • 238
  • Hmm... but isn't it strange that this flag isn't in `-Wall` nor even in `-Wextra`? – einpoklum Mar 26 '21 at 19:46
  • What's included in `-Wall` pr `-Wextra` may not be consistent across gcc versions.`-Wnull-reference` is, as documented, included only when optimizations are enabled (in my command line, it's actually `-O3` that enables it). – P.P Mar 26 '21 at 19:53
  • .. I was just about so say that. Detecting the error by static analysis requires the kind of _abstract execution_ that the optimiser performs for it to be detected. – Clifford Mar 26 '21 at 19:56
  • I got a warning with `gcc -Wall -Wextra -O3 -Wnull-dereference`. I *didn't* get a warning with `gcc -Wall -Wextra -O3`. – Keith Thompson Mar 26 '21 at 20:09
  • Merely dereferencing a null pointer is **not** undefined behavior. – SergeyA Mar 26 '21 at 20:13
  • @KeithThompson Looks like both flags are needed. – P.P Mar 26 '21 at 20:18
  • 1
    @SergeyA [6.5.3.2 Address and indirection operators/4](https://port70.net/~nsz/c/c11/n1570.html#6.5.3.2p4) says: **If an invalid value has been assigned to the pointer, the behavior of the unary \* operator is undefined**. And footnote 102 further clarifies an invalid pointer: **Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer [..]**. – P.P Mar 26 '21 at 20:28
  • @P.P, no, it is not. The only time where null pointer comes into discussion for indirection is a note 2 to 9.3.4.3 (using latest standard), but this is not normative, and it was already discussed multiple times before. See https://stackoverflow.com/questions/1110111/what-part-of-dereferencing-null-pointers-causes-undesired-behavior/1110370#1110370 for longer write-up. – SergeyA Mar 26 '21 at 20:47
1

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.

klutt
  • 30,332
  • 17
  • 55
  • 95
  • 1
    the compiler si so smat that when optimization is on, the call to printf is removed because it "sees" the undefine behaviour https://godbolt.org/z/T5eh91dj8 – OznOg Mar 26 '21 at 19:28
  • @OznOg True. I'll clarify it. – klutt Mar 26 '21 at 19:28
  • "It's simply because the compilers are not smart enough to do it." <- But they are, at least in simple cases. See P.P's [answer](https://stackoverflow.com/a/66823178/1593077). – einpoklum Mar 26 '21 at 19:47
  • @einpoklum Yes, but not smart enough for the case OP has in the question – klutt Mar 26 '21 at 19:57
  • @klutt: No, it's smart enough for the case I have in the question. We just need to give it the right switch. – einpoklum Mar 26 '21 at 19:58
0

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157