1

Well, how does the stack work? For example the instruction:

push ax

is equal to:

sub sp, 4
mov sp, ax

where sp is a stack pointer. Is that right?

My question is - what's the point of subtracting 4 from the sp register if a moment later I change it to the whole different value?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
user2489034
  • 239
  • 2
  • 3
  • 15

4 Answers4

6

I think that's supposed to read

sub  sp, 2       ; AX is only 2 bytes wide, not 4
mov [sp], ax     ; store to memory, not writing the register

That is, put the value of ax into the memory pointed to by sp.

Perhaps your sub sp, 4 came from pushing a 32-bit register? The stack pointer always decreases by the push operand-size.

(Note that push doesn't modify FLAGS, unlike sub. This pseudocode / equivalent isn't exactly equivalent, also not for the push sp case. See Intel's manual, or this Q&A for pseudocode that works even in those cases.)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
AMADANON Inc.
  • 5,753
  • 21
  • 31
1

That's not how push ax works. Your code example of what it is "equal to" should be:

sub sp, 2
mov [ss:sp], ax

You don't overwrite the value of SP with AX. You instead copy AX into the memory address pointed to by SS:SP (using the stack segment, rather than the data segment)... But actually, this isn't even accurate. What you really need is something like this:

mov [tmp], sp
pushf          ;push FLAGS
sub [tmp], 2
popf
mov sp, [tmp]
mov [ss:sp], ax

Basically, push does something quite simple, but the details around that simple thing make it very worth making it's own instruction. Especially being able to copy memory-to-memory with instructions like push word [bp - 4] to push a local variable if you didn't already have it loaded.

Alternate code that doesn't need, and doesn't use the imaginary [ss:sp] addressing mode that's not encodeable as a 16-bit addressing mode.

mov internal_tmp, sp
lea internal_tmp, [internal_tmp - 2]   ; sub without FLAGS
mov [internal_tmp], SRC_OPERAND        ; even for a memory source, or for push sp
mov sp, internal_tmp
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Earlz
  • 62,085
  • 98
  • 303
  • 499
0

You're moving the value of ax into the next available stack frame. The stack pointer is built downwards (so that if you have too many values it underflows beyond zero and raises an error instead of overflowing into something important), so the next available stack frame location is one word (4 bytes) before the current location.

PaulProgrammer
  • 16,175
  • 4
  • 39
  • 56
0

The difference is that sub esp, 4 will set flags, but push will not set flags. Also, it should be mov [esp],eax. The 16-bit versions you show indicates you are using a very old book. Nobody programs on a 16-bit machine anymore (except embedded microcontrollers, possibly).

On an x86 the OF, SF, ZF, ZF, PF and CF flags will be set by the subtraction, but no flags are affected by a PUSH.

Tyler Durden
  • 11,156
  • 9
  • 64
  • 126
  • I'd say the 16-bit instructions are much easier to understand before moving onto the 32-bit instructions, especially around areas like the SIMD addressing and protected mode and all of that difficult stuff for a beginner to grok in the beginning – Earlz Jul 02 '13 at 20:57
  • @Earlz: The non-orthogonal set of 16-bit addressing modes, and the need for segmentation at all, makes 16-bit mode more complicated. 32-bit *user-space* is simpler (and better at detecting bugs by faulting on bad pointers); we're not talking about osdev stuff where you'd have to set up protected mode yourself! – Peter Cordes Oct 21 '21 at 11:36