All answer will probably talk about "undefined behavior", since you are attempting the logical nonsense of modifying a constant.
Although this is technically perfect, let me give you some hints about why this happens (about "how", see Mysticial answer).
It happens because C++ is by design an "imperfectly specified language". The "imperfection" consist in a number of "undefined behaviors" that pervade the language specification.
In fact, language designers deliberately choose that -in some circumstances- instead of say "if you do this, will gave you that", (that may be: you got this code, or you got this error) thay prefer to say "we don't define what will happen".
This lets the compiler manufacturers free to decide what to do. And since there are many compiler working on many platforms, may be the optimal solution for one in not necessarily the optimal solution for another (that may have rely to a machine with a different instruction set) and hence you (as a programmer) are left in the dramatic situation that you'll never know what to expect, and even if you test it, you cannot trust the result of the test, since in another situation (compiling the same code with a different compiler or just a different version of it, or for a different platform) it will be different.
The "bad" thing, here, is that a compiler should warn when an undefined behavior is hit (forcing a const should be warned as a potential bug, especially if the compiler does const-inlining otimizations, since it is a nonsense if a const is allowed to be changed), as mot likely it does, if you specify the proper flag (may be -W4 or -wall or -pedantic or similar, depending of the compiler you have).
In particular the line
int *ptr = (int *) &i;
should issue a warning like:
warning: removing cv-qualifier from &i.
So that, if you correct your program as
const int *ptr = (const int *) &i;
to satisfy the waarning, you wil get an error at
*ptr = 99;
as
error: *ptr is const
thus making the problem evident.
Moral of the story:
From a legal point of view, you wrote bad code since it is -by language definition- relying on undefined behavior.
From a moral point of view: the compiler kept an unfair behavior: performing const-inlining (replacing cout << i
with cout << 5
) after accepting (int*)&i
is a self-contradition, and incoherent behavior should at least be warned.
If it wants to do one thing must not accept the other, or vice-versa.
So check if there is a flag you can set to be warned, and if not, report to the compiler manufacturer its unfairness: it didn't warn about its own contradiction.