2

While debugging a program of mine, I stumbled upon a weird behaviour of the gcc compiler. I don't know what's the correct title to describe this, but take a look at the code below.

Basically, I had a function which received a void* arg as an argument. It then casted it to a pointer of another type, HTTPRequest*. However, I casted the wrong variable, so the code looked like

void someCallback(void* arg) {
   HTTPRequest* req = (HTTPRequest*) req;
   FreeRequest(req);
   //re-setup request, other stuff.. 
}

The program then crashed within FreeRequest with a SIGSEGV when it tried to derefrence the pointer. Upon further inspection, req always a had a value of NULL.

It took me a while to realize that the cast I did was just done on the wrong variabe - it should have been

   HTTPRequest* req = (HTTPRequest*) arg;

Then it all worked. I was however baffled that gcc allowed me to not only compile this code, but throw no warning whatsoever at this line.

Consider the minimal example

#include <stdlib.h>
#include <stdio.h>

void someFunc(void* arg) {  
    int* a = (int*) a; 

    printf("a = %p\n", a);
    printf("*a = %d\n", *a);
}

int main() {

    int b = 42;
    someFunc(&b);

    return 0;
}

Compiled with

gcc -Wall -Wextra -Wpedantic -o test -O0 test.c && test.exe

Compiler outputs

test.c: In function 'someFunc':
test.c:7:9: warning: format '%p' expects argument of type 'void *', but argument 2 has type 'int *' [-Wformat=]
  printf("a = %p\n", a);
         ^
test.c:4:21: warning: unused parameter 'arg' [-Wunused-parameter]
 void someFunc(void* arg) {
                     ^

And the program outputs:

a = 0061FFCC
*a = 6422500

With optimization at atleast O1 it outputs however:

gcc -Wall -Wextra -pedantic -o test -O1 test.c && test.exe
test.c: In function 'someFunc':
test.c:7:9: warning: format '%p' expects argument of type 'void *', but argument 2 has type 'int *' [-Wformat=]
  printf("a = %p\n", a);
         ^
test.c:4:21: warning: unused parameter 'arg' [-Wunused-parameter]
 void someFunc(void* arg) {
                     ^

And outputs

a = 00000000

Then hangs.

So the question is: Why does gcc allow the compilation of expressions of the above form? This is obviously undefined behaviour. Then why are there no warnings about this, even with all warnings enabled?

Maximilian Gerhardt
  • 5,188
  • 3
  • 28
  • 61
  • What it basically boils down to is the usual uninitialized local variable thing. The cast itself makes no difference, it's that you dereference an uninitialized pointer, whose value is *indeterminate* and therefore have *undefined behavior*. – Some programmer dude Jun 17 '17 at 10:54
  • As for why GCC allows the "initialization" is because it's syntactically valid. I *am* surprised that it doesn't warn about it though. What version of GCC are you using? – Some programmer dude Jun 17 '17 at 10:55
  • @pgm Yeah, since `void*` can be cast to anything implicitly, you're right. But the core of the requestion is why it compiles statements of the form `int* a = (int*)a`). @Someprogrammerdude: `gcc version 5.3.0 (GCC)` – Maximilian Gerhardt Jun 17 '17 at 10:57
  • 1
    An answer to a similar question I wrote a few months ago on why gcc behaves this way: https://stackoverflow.com/questions/42365507/does-this-self-assignment-do-something-sensible/42367381#42367381 – Art Jun 17 '17 at 11:01
  • Initializing an uninitialized pointer by itself is apparently confusing `gcc`'s non-initialized value detection mechanism (which is not a mandatory feature according to the standard) - after all, the pointer *is* initialized with something. I would consider this a compiler flaw. – tofro Jun 17 '17 at 11:01
  • 2
    @tofro It's not a compiler bug. This is done by design (which I found out a few weeks after I wrote the answer I linked to). There is a warning for that but it is disabled by default. – Art Jun 17 '17 at 11:03
  • 2
    @pmg: "*If you had omitted the erroneous cast, the compiler would have complained.*" are you sure? GCC would have complained about exactly what then, for doing `int* a = a;`? – alk Jun 17 '17 at 11:10
  • Note : **all** the casts in the above question are not necessary. (assuming `int *a = arg;` was intended) – wildplasser Jun 17 '17 at 13:11

1 Answers1

3

Why does gcc allow the compilation of expressions of the above form? This is obviously undefined behaviour.

Because the standard allow this. Undefined behavior allow compiler to optimize code write in C. You can read this excellent article from llvm developer.

Then why are there no warnings about this, even with all warnings enabled?

-Wall -Wextra don't active all warning in gcc. clang has -Weverything but, gcc don't have such option.

Compiler are not force to print a warning message for everything. There are few warning that a compiler must print if it want to be standard compliant.

What you do is undefined behavior and this is not mandatory in the standard to print a warning.

clang 4.0.0 has a warning for this -Wuninitialized:

warning: variable 'a' is uninitialized when used within its own initialization [-Wuninitialized]
    int* a = (int*) a;

gcc 7.1.1 has a warning for this -Winit-self:

warning: ‘a’ is used uninitialized in this function [-Wuninitialized]
     int* a = (int*) a;

Note: Your cast is useless, C will promote any void * to the correct pointer type.

Stargateur
  • 24,473
  • 8
  • 65
  • 91