This is the subject of register allocation. Basically what is done is the compiler computes for each variable, what other variables are in use at the same time. The compiler will then construct an interference graph, where there is a node for each variable used in the program, and an edge between all nodes that are live at the same time. This then becomes a graph coloring problem, where the colors correspond to the registers available on the machine.
As you may know, graph coloring is an NP-Complete problem, so compilers implement a simple, yet very effective heuristic. Basically, they find the highest degree node in the graph that has fewer than k edges, where k is the number of registers on the machine. We then remove this node along with all of its edges and recursively color the remaining graph. If no such node exists, we take the highest degree node, and spill it, meaning we store it on the stack instead, and retry the coloring process with that node removed.