Although there are simple cases like your where compilers and/or static analyzers could detect that an access is out of bounds, doing it in general at compile-time is not doable. For example, if you pass off your array to a function it immediately decays into a pointer and the compiler has no chance to do bounds checking at compile-time.
The alternative, run-time bounds checking, is comparatively expensive: doing a check upon each access would turn a simple memory dereference into a potentially stalling branch. To make things even harder, you can use the dereference operator on pointers, i.e., you can't even know easily where to locate the size of the actual array object.
As a result, the behavior of out of bounds array accesses is deliberately made undefined: a system can track these accesses but it doesn't have to. Also, what the system actually does upon an out of bounds array access is not specified, i.e., it can do different things depending on the context. In many cases, it will just return junk which isn't really too useful. However, especially with suitable debug settings the system may instead assert()
upon detecting a violation.