* Optimizer bugs. Thankfully these are less common nowadays. It the "good old days" of new compilers this happened quite a bit.
Embedded code:
* Missing "volatiles" which allow the optimizer to optimize out "unused" loads and stores to hardware or multitasking shared variables.
* Race conditions (e.g. unsynchronized access to multitasking shared variables). Making the code run slower changes the access pattern, often times obscuring the bug.
Crap code that hit undefined behaviour every other line (like accessing dead temporaries or freed memory, assuming stack layout, out of bound accesses, etc.).
I don't have an answer to that, but I have seen - and worked with - several codebases which work fine when compiled with -g, but will crash (in good cases) or behave irrationally (in bad cases) without.
The crashing ones at least are easy. Somewhere a list or variable-argument array is missing the NULL terminator...
This was on Windows with VC++, but same deal. The code that some cow-orkers had written was copying strings like "LAX" and "ORD" into `char airport[3]` using strcpy(). In debug builds, VC was allocating a whole 32-bit word, but in release builds it was packing everything on the stack. Write to it, and the terminating null ends up overwriting a byte of the next variable on the stack. Urgh. Of course, these were several hundred line functions, so the strcpy and the subsequent use of the trashed variable were a long ways away.