Why uninitialized instead of out-of-bounds?
I believe this could be the case here: in the first code, GCC notices that you don't need the entire char array at all, just b[9]
, so it can replace the code with
char b_9; // = ???printf("b[9] = %d\n", b_9);
Now, this is a completely legal transform, because as the array was accessed out of bounds, the behaviour is completely undefined. Only in latter phase does it then notice that this variable, which is a substitute for b[9]
, is uninitialized, and issues the diagnostics message.
Why I believe this? Because if I add just any code that will reference the array's address in memory, for example printf("%p\n", &b[8]);
anywhere, the array now is fully realized in memory, and compiler will diagnose array subscript is above array bounds.
What I find even more interesting is that GCC does not diagnose out-of-bounds access at all unless optimizations are enabled. This would again suggest that whenever you're writing a program new program you should compile it with optimizations enabled to make the bugs highly visible instead of keeping them hidden with debug mode ;)
The behaviour on reading b[9]
or b[10]
is undefined.
Your compiler is issuing a warning (it doesn't have to), although the warning text is a little misleading, but not technically incorrect. In my opinion, it's rather clever. (A C compiler is not required to issue a diagnostic for out of bounds access.)
Regarding &b[9]
, the compiler is not allowed to dereference that, and must evaluate it as b + 9
. You are allowed to set a pointer one past the end of an array. The behaviour of setting a pointer to &b[10]
is undefined.
Some additional experimental results.
Using char b[9]
instead of char b[]
appears to make no difference, gcc still warns the same with char b[9]
.
Interestingly, initializing the one-passed element via the "next" member in a struct
1) does quiet the "uninitialized" warning and 2) does not warn about accessioning outside the array.
#include <stdio.h>typedef struct { char c[9]; char d[9];} TwoNines;int main(void) { char b[9] = { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' }; printf("b[] size %zu\n", sizeof b); printf("b[9] = %d\n", b[9]); // 'b[9]' is used uninitialized in this function [-Wuninitialized] TwoNines e = { { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' }, // { 'N', 'i', 'c', 'e', ' ', 'y', 'o', 'u', '!' } }; printf("e size %zu\n", sizeof e); printf("e.c[9] = %d\n", e.c[9]); // No warning. return 0;}
Output
b[] size 9b[9] = 0e size 18 // With 18, we know `e` is packed.e.c[9] = 78 // 'N'
Notes:
gcc -std=c11 -O3 -g3 -pedantic -Wall -Wextra -Wconversion -c -fmessage-length=0 -v -MMD -MP ...
gcc/gcc-7.3.0-2.i686