The indexing operator a[b]
in C and C++ can be considered defined as *(&a + sizeof(TA) * b)
, (interestingly, a[b]
and b[a]
are also equivalent, but that's another explanation).
Let's walk through the code:
char a[2]; // statically-allocates 2 bytes on the stack, e.g. at `0xFFFF`, and `0xFFFE` (as the stack grows downwards)
a[0] = 'a'; // sets `0xFFFF`
a[1] = 'b'; // sets `0xFFFE`
a[2] = 'c'; // sets `0xFFFD` <-- danger!
Here lies the danger: C/C++ does not mandate that array code have bounds-checking, so your code can be considered equivalent to this:
char a0; // 0xFFFF
char a1; // 0xFFFE
*0xFFFF = 'a';
*0xFFFE = 'b';
*0xFFFD = 'c'; <-- danger! writing to unallocated memory
*0xFFFC = 'd'; <-- uncharted territory! here be dragons!
Your code "works" because 0xFFFD
will be memory that exists for two reasons: 1: The stack grows downward and will be reserved by the OS automatically so you won't segfault ("access violation" on Windows), and 2: you aren't coming close to a stack overflow error condition.
However if you were to add more local variables to your function then you'll see that a[2]
will overrwrite those values, you also run the risk of overwriting the current stack frame's return address thus corrupting your stack and rendering your program in an indeterminate state that should be terminated immediately).
Consider:
char[2] a;
int b = 0;
int c = 0;
a[2] = 'a';
assert( b == 0 ); // this assertion will fail (at least on systems that don't word-align locals)