Other people have explained that this code is perfectly valid. This answer is about your expectation that, if the code had been invalid, there would have been a runtime error when calling printf
. It isn't necessarily so.
Let's look at this variation on your code, which is invalid:
#include <stdio.h>
int main(void)
{
int *a;
{
int b = 42;
a = &b;
}
printf("%d\n", *a); // undefined behavior
return 0;
}
This program has undefined behavior, but it happens to be fairly likely that it will, in fact, print 42, for several different reasons — many compilers will leave the stack slot for b
allocated for the entire body of main
, because nothing else needs the space and minimizing the number of stack adjustments simplifies code generation; even if the compiler did formally deallocate the stack slot, the number 42 probably remains in memory until something else overwrites it, and there's nothing in between a = &b
and *a
to do that; standard optimizations ("constant and copy propagation") could eliminate both variables and write the last-known value for *a
directly into the printf
statement (as if you had written printf("%d\n", 42)
).
It's absolutely vital to understand that "undefined behavior" does not mean "the program will crash predictably". It means "anything can happen", and anything includes appearing to work as the programmer probably intended (on this computer, with this compiler, today).
As a final note, none of the aggressive debugging tools I have convenient access to (Valgrind, ASan, UBSan) track "auto" variable lifetimes in sufficient detail to trap this error, but GCC 6 does produce this amusing warning:
$ gcc -std=c11 -O2 -W -Wall -pedantic test.c
test.c: In function ‘main’:
test.c:9:5: warning: ‘b’ is used uninitialized in this function
printf("%d\n", *a); // undefined behavior
^~~~~~~~~~~~~~~~~~
I believe what happened here was, it did the optimization I described above — copying the last known value of b
into *a
and then into the printf
— but its "last known value" for b
was a "this variable is uninitialized" sentinel rather than 42. (It then generates code equivalent to printf("%d\n", 0)
.)