>>2 is correct, assuming you're compiling on a x86.
And it is not the case that the program
should crash in the first place.
If you overwrite your return address with a valid address, the program will continue to run, and may even run correctly depending on your input. That is actually the basis for several buffer overflow exploits.
If your routine
never returns (for example, an infinite loop or an early call to
exit()
), your program won't crash, even if you overwrite the entire stack space before it.
Undefined behavior means undefined behavior
standard-wise. It is only a catch-all situation: a wildcard in the sense that
any behavior produced by an implementation is
correct according to the standard.
In this particular case, the program behavior is well-defined and consistent, as you might have perceived, and this behavior is determined by the underlying environment. The compiler might insert some code to protect against stack smashing, but in the situation of the normal call-ret pair, with or without a frame pointer involved, overwriting stack addresses only affect your own local variables and any or all of your callers. You can, for example, make you or any caller return to
another function or block of code instead of returning to their respective callers.
Walking forward in the stack, on the other hand, is a typical hack used to create variables with "discardable" semantics, that is: variables which are not preserved across function calls, which do not grow the stack and without the need to allocate or deallocate heap memory. Care must be taken to not overrun the stack size, that is, overflow the stack itself.
This, naturally, applies only to the x86 architecture. In other environments the behavior may differ completely.