3.8 The stack Segment and the push and pop Instructions

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.