How a compiler generally works for a microcontroller
Because of the type of questions you've asked, I assume you know the basics of compiling, i.e. taking a high-level language like C, do a lexical scan, parsing, and then compiling it to individual machine instructions which include the opcodes and parameters (immediate operands, registers, or addresses) for each instruction.  This usually is first compiled to relocatable object code, which can be linked together with other relocatable modules to create the final binary image.

I am ignoring compilers like Java and .NET which compile to an intermediate bytecode and then do JIT (just-in-time) compiling as needed; these are generally not found on embedded systems.

There are three types of data that can be allocated external to a function or procedure; the first is uninitialized data (historically called BSS), and the second is initialized (non-BSS). As a program is compiled, variables will be allocated to one of these two general areas (unless directed by the programmer to go into a special data segment).  During the linking stage, all of the data items will be collected together.  All variables which need to be initialized will have a portion of the program memory set aside to hold the initial values, and just before main() is called, the variables will be initialized.  The BSS section is initialized to all zeros.

The third type of data external to a function or procedure is like the initialized variables, except it is read-only and cannot be modified by the program.  In the C language, these variables are denoted using the 'const' keyword.  They are usually stored as part of the program flash memory.

These variables can be either global (shared between different modules in the same program) or local to a particular module (indicated by having the variable preceded by the keyword 'static' in C.  The static keyword is also used in C to designate variables within a function or procedure that retain their value across multiple calls of the function or procedure.

There are two other kinds of variables, those on the stack and those on the heap.  C calls stack variables 'automatic'.  They are any variables defined within a function or procedure without the keyword static.  Because they are on the stack, they allow for both recursive and re-entrant functions.  Some processors have a stack frame register that allows easy access to these variables via an offset.

Parameters passed to a function or procedure are also passed on the stack.

Some processors don't have a stack for data, in which case the compiler has to fake a way to pass parameters as if a stack existed.  Usually these compilers don't allow recursion or reentrant calls..

Dynamic variables are allocated on the heap using a function like alloc or new, and are disposed of using free or delete.  For languages with garbage collection (rare on embedded systems), explicit freeing of dynamic memory is not required.

During the designing of a program, it is important for the programmer to consider the implications of where a particular variable is defined; particular if it uses a fair amount of RAM (such as a an array).

With a few microcontrollers, there are shorter instructions that allow access to the first page (first 256 locations) of RAM.  The compiler for these processors may reserve a keyword like 'near' to designate variables to be placed there.  Similarly, there are also microcontrollers that can only reference certain areas via a pointer register (requiring extra instructions), and such variables are designated 'far'.  Finally, some processors can address a section of memory bit by bit.

There is usually a default value for the stack size, perhaps only a few KB, but that can by changed by a linker directive.  Depending on the architecture of the processor, the stack may grow up, or grow down.  If it grows up, it will be placed at the bottom of RAM.  If it grows down, it will be placed at the end of RAM.  The heap will use the remaining RAM not allocated to variables, and grow the opposite direction of the stack.

Virtually all embedded systems use a stack, at least for storing call/return addresses; but heaps are usually not used on microcontrollers with a small amount of RAM due to the likelihood of extensive memory fragmentation.

There are several types of optimization.  Many many books running hundreds of pages long have been written on the subject.  On PCs with lots of RAM, optimization is almost always directed for speed.  But on programs targeted for microcontrollers, optimizing for size might be more important.  Or a mixture of both.

The simplest is peephole or window optimization, which means combinations of instructions can be reduced to just one, or redundant loads and stores can be eliminated.  The compiler will try to keep local variables in registers as much as possible -- this is particular applicable now that many processors have 16 or even 32 general purpose registers.

Loop optimization tries to move any operations done within a loop, that don't really need to be inside the loop (so called loop-invariant instructions), out of the loop.  This can make a big difference in execution time.

Sometimes variables have to be shared between both the base-level and interrupt routines.  The C language provides a keyword called volatile that tells the compiler not to do any optimization on these variables (such as keeping them in registers between statements).

You almost always want to turn optimization off when debugging programs, since the compiler may rearrange instructions and if you try to step through the program it may appear to hop around very mysteriously.

Besides considering where to place variables, a programmer should try to select an efficient stack size.  Sometimes there will be tools to let you know how much stack has been used.  If not, If you initialize the stack area to some known value, such as 0x55 before the program is run, then run your program for awhile, stop it and examine the stack area, you will be able to figure out how much stack was used.