The code produced by a compiler is usually well-behaved: it uses well-defined calling conventions, keeps the stack balanced, does not self-modify at all or limits self-modification to certain sites.
Incidentally, this is one of the ways in which you can distinguish between compiler output and human-written Asm... and likewise, why compilers just "aren't quite there" in terms of "extreme" optimisation. Calling conventions are pretty much the first thing to go; it's common to have functions that return as many values as there are registers, use a per-function combination of registers for arguments/return values, or access its caller's "local variables" directly --- all of which compilers would find very difficult to produce via optimisation even if it could be expressed in the higher-level language.
TLDR; Compilers break a for loop into three distinct loop portions -- a main loop where special invariants are satisfied (and thus less checks are required), and pre/post loops with additional bounds checks.
Incidentally, this is one of the ways in which you can distinguish between compiler output and human-written Asm... and likewise, why compilers just "aren't quite there" in terms of "extreme" optimisation. Calling conventions are pretty much the first thing to go; it's common to have functions that return as many values as there are registers, use a per-function combination of registers for arguments/return values, or access its caller's "local variables" directly --- all of which compilers would find very difficult to produce via optimisation even if it could be expressed in the higher-level language.