You can push more than one value onto the stack without first popping previous values off the stack. However, the stack is a last-in, first-out (LIFO) data structure, so you must be careful how you push and pop multiple values. For example, suppose you want to preserve EAX and EBX across some block of instructions; the following code demonstrates the obvious way to handle this:
push( eax ); push( ebx ); << Code that uses eax and ebx goes here. >> pop( eax ); pop( ebx );
Unfortunately, this code will not work properly! Figure 3-13 through Figure 3-16 show the problem. Because this code pushes EAX first and EBX second, the stack pointer is left pointing at EBX's value on the stack. When the pop( eax );
instruction comes along, it removes the value that was originally in EBX from the stack and places it in EAX! Likewise, the pop( ebx );
instruction pops the value that was originally in EAX into the EBX register. The end result is that this code manages to swap the values in the registers by popping them in the same order that it pushes them.
To rectify this problem, you must note that the stack is a last-in, first-out data structure, so the first thing you must pop is the last thing you push onto the stack. Therefore, you must always observe the following maxim:
The correction to the previous code is:
push( eax ); push( ebx ); << Code that uses eax and ebx goes here. >> pop( ebx ); pop( eax );
Another important maxim to remember is:
This generally means that the number of pushes and pops must exactly agree. If you have too few pops, you will leave data on the stack, which may confuse the running program. If you have too many pops, you will accidentally remove previously pushed data, often with disastrous results.
A corollary to the maxim above is, "Be careful when pushing and popping data within a loop." Often it is quite easy to put the pushes in a loop and leave the pops outside the loop (or vice versa), creating an inconsistent stack. Remember, it is the execution of the push and pop instructions that matters, not the number of push and pop instructions that appear in your program. At runtime, the number (and order) of the push instructions the program executes must match the number (and reverse order) of the pop instructions. |
The 80x86 provides several additional push
and pop
instructions in addition to the basic push
/pop
instructions. These instructions include the following:
The pusha
instruction pushes all the general-purpose 16-bit registers onto the stack. This instruction exists primarily for older 16-bit operating systems like MS-DOS. In general, you will have very little need for this instruction. The pusha
instruction pushes the registers onto the stack in the following order:
ax cx dx bx sp bp si di
The pushad
instruction pushes all the 32-bit (double-word) registers onto the stack. It pushes the registers onto the stack in the following order:
eax ecx edx ebx esp ebp esi edi
Because the pusha
and pushad
instructions inherently modify the SP/ESP register, you may wonder why Intel bothered to push this register at all. It was probably easier in the hardware to go ahead and push SP/ESP rather than make a special case out of it. In any case, these instructions do push SP or ESP, so don't worry about it too much—there is nothing you can do about it.
The popa
and popad
instructions provide the corresponding "pop all" operation to the pusha
and pushad
instructions. This will pop the registers pushed by pusha
or pushad
in the appropriate order (that is, popa
and popad
will properly restore the register values by popping them in the reverse order that pusha
or pushad
pushed them).
Although the pusha
/popa
and pushad
/popad
sequences are short and convenient, they are actually slower than the corresponding sequence of push
/pop
instructions, this is especially true when you consider that you rarely need to push a majority, much less all, of the registers.[44] So if you're looking for maximum speed, you should carefully consider whether to use the pusha
(d
)/popa
(d
) instructions.
The pushf
, pushfd
, popf
, and popfd
instructions push and pop the EFLAGS register. These instructions allow you to preserve condition code and other flag settings across the execution of some sequence of instructions. Unfortunately, unless you go to a lot of trouble, it is difficult to preserve individual flags. When using the pushf
(d
) and popf
(d
) instructions, it's an all-or-nothing proposition—you preserve all the flags when you push them; you restore all the flags when you pop them.
Like the pushad
and popad
instructions, you should really use the pushfd
and popfd
instructions to push the full 32-bit version of the EFLAGS register. Although the extra 16 bits you push and pop are essentially ignored when writing applications, you still want to keep the stack aligned by pushing and popping only double words.
Once in a while you may discover that you've pushed data onto the stack that you no longer need. Although you could pop the data into an unused register or memory location, there is an easier way to remove unwanted data from the stack—simply adjust the value in the ESP register to skip over the unwanted data on the stack.
Consider the following dilemma:
push( eax );
push( ebx );
<< Some code that winds up computing some values we want to keep
into eax and ebx >>
if( Calculation_was_performed
) then
// Whoops, we don't want to pop eax and ebx!
// What to do here?
else
// No calculation, so restore eax, ebx.
pop( ebx );
pop( eax );
endif;
Within the then
section of the if
statement, this code wants to remove the old values of EAX and EBX without otherwise affecting any registers or memory locations. How can we do this?
Because the ESP register contains the memory address of the item on the top of the stack, we can remove the item from the top of stack by adding the size of that item to the ESP register. In the preceding example, we wanted to remove two double-word items from the top of stack. We can easily accomplish this by adding 8 to the stack pointer (see Figure 3-17 and Figure 3-18 for the details):
push( eax );
push( ebx );
<< Some code that winds up computing some values we want to keep
into eax and ebx >>
if( Calculation_was_performed
) then
add( 8, ESP ); // Remove unneeded eax/ebx values from the stack.
else
// No calculation, so restore eax, ebx.
pop( ebx );
pop( eax );
endif;
Effectively, this code pops the data off the stack without moving it anywhere. Also note that this code is faster than two dummy pop
instructions because it can remove any number of bytes from the stack with a single add
instruction.
Remember to keep the stack aligned on a double-word boundary. Therefore, you should always add a constant that is a multiple of 4 to ESP when removing data from the stack.
[44] For example, it is extremely rare for you to need to push and pop the ESP register with the pushad
/popad
instruction sequence.