This chapter mentions that all variables you declare in the var
section wind up in the stack
memory segment. However, var
objects are not the only things in the stack
memory section; your programs manipulate data in the stack
segment in many different ways. This section describes the stack and introduces the push
and pop
instructions that manipulate data in the stack
section.
The stack
segment in memory is where the 80x86 maintains the stack. The stack is a dynamic data structure that grows and shrinks according to certain needs of the program. The stack also stores important information about the program including local variables, subroutine information, and temporary data.
The 80x86 controls its stack via the ESP (stack pointer) register. When your program begins execution, the operating system initializes ESP with the address of the last memory location in the stack
memory segment. Data is written to the stack
segment by "pushing" data onto the stack and "popping" data off the stack.
Consider the syntax for the 80x86 push
instruction:
push(reg16
); push(reg32
); push(memory16
); push(memory32
); pushw(constant
); pushd(constant
);
These six forms allow you to push word
or dword
registers, memory locations, and constants. You should specifically note that you cannot push byte
values onto the stack.
The push
instruction does the following:
ESP := ESP -Size_of_Register_or_Memory_Operand
(2 or 4) [ESP] :=Operand's_Value
The pushw
and pushd
operands are always 2- and 4-byte constants, respectively.
Assuming that ESP contains $00FF_FFE8, then the instruction push( eax );
will set ESP to $00FF_FFE4 and store the current value of EAX into memory location $00FF_FFE4, as Figure 3-9 and Figure 3-10 show.
Note that the push( eax );
instruction does not affect the value of the EAX register.
Although the 80x86 supports 16-bit push operations, their primary use in is 16-bit environments such as MS-DOS. For maximum performance, the stack pointer's value should always be an even multiple of 4; indeed, your program may malfunction under a 32-bit OS if ESP contains a value that is not a multiple of 4. The only practical reason for pushing less than 4 bytes at a time on the stack is to build up a double word via two successive word pushes.
To retrieve data you've pushed onto the stack, you use the pop
instruction. The basic pop
instruction allows the following forms.
pop(reg16
); pop(reg32
); pop(memory16
); pop(memory32
);
Like the push
instruction, the pop
instruction supports only 16-bit and 32-bit operands; you cannot pop an 8-bit value from the stack. As with the push
instruction, you should avoid popping 16-bit values (unless you do two 16-bit pops in a row) because 16-bit pops may leave the ESP register containing a value that is not an even multiple of 4. One major difference between push
and pop
is that you cannot pop a constant value (which makes sense, because the operand for push
is a source operand, while the operand for pop
is a destination operand).
Formally, here's what the pop
instruction does:
Operand
:= [ESP] ESP := ESP +Size_of_Operand
(2 or 4)
As you can see, the pop
operation is the converse of the push
operation. Note that the pop
instruction copies the data from memory location [ESP] before adjusting the value in ESP. See Figure 3-11 and Figure 3-12 for details on this operation.
Note that the value popped from the stack is still present in memory. Popping a value does not erase the value in memory; it just adjusts the stack pointer so that it points at the next value above the popped value. However, you should never attempt to access a value you've popped off the stack. The next time something is pushed onto the stack, the popped value will be obliterated. Because your code isn't the only thing that uses the stack (for example, the operating system uses the stack as do subroutines), you cannot rely on data remaining in stack memory once you've popped it off the stack.
Perhaps the most common use of the push
and pop
instructions is to save register values during intermediate calculations. A problem with the 80x86 architecture is that it provides very few general-purpose registers. Because registers are the best place to hold temporary values, and registers are also needed for the various addressing modes, it is very easy to run out of registers when writing code that performs complex calculations. The push
and pop
instructions can come to your rescue when this happens.
Consider the following program outline:
<< Some sequence of instructions that use the eax register >> << Some sequence of instructions that need to use eax, for a different purpose than the above instructions >> << Some sequence of instructions that need the original value in eax >>
The push
and pop
instructions are perfect for this situation. By inserting a push
instruction before the middle sequence and a pop
instruction after the middle sequence above, you can preserve the value in EAX across those calculations:
<< Some sequence of instructions that use the eax register >> push( eax ); << Some sequence of instructions that need to use eax, for a different purpose than the above instructions >> pop( eax ); << Some sequence of instructions that need the original value in eax >>
The push
instruction above copies the data computed in the first sequence of instructions onto the stack. Now the middle sequence of instructions can use EAX for any purpose it chooses. After the middle sequence of instructions finishes, the pop
instruction restores the value in EAX so the last sequence of instructions can use the original value in EAX.