You mention that main not having a return statement is undefined behaviour. While this would be true for most functions the main function is actually a special case.
If you check section 18.104.22.168.3 - Program termination of the standard doc you linked it says that "reaching the } that terminates the main function returns a value of 0".
This is obviously a super minor nitpick though and I enjoyed the post!
Always good to see more compiler articles, even if a bit "traditional" (for lack of a better term). For example, I don't think I've seen one yet which makes use of the precedence climbing method to dramatically simplify the parser and make it more table-driven/declarative. This is extremely useful in a language like C, with many precedence levels.
No global variables.
Global variables are much simpler to implement since they share the same namespace as functions, and always have a fixed address.
We could store the variable’s offset from ESP, except that the value of ESP changes whenever we push something onto the stack. The solution is to store the variable’s offset from a different register, EBP.
You can also keep track of the stack offset, since you're generating the code and know what's been pushed/popped. Using ESP-relative accesses makes the code slightly larger (due to SIB byte) but is often used since it frees up EBP for more useful things. Of course this is a somewhat moot point if you're not going to use it anyway, but speaking as someone who has read a lot of compiler output over the years, ESP-relative accesses seem to be somewhat more common in released code.
This is the fifth post in a series. Read part 1 here.
It would be better to also link to the previous part, for those who just want to read what came immediately before and not start again from the beginning.