This all depends on the calling convention of course but it generally goes like this.
The stack pointer is able to be moved arbitrarily based on whatever you need to push onto, or pop off from, the stack at any given point in time. This can happen anytime within a function, as you need to temporarily save some data on the stack.
The base pointer is generally set to the same value for any given stack depth, and is used to access passed parameters (on one side) and local variables (on the other side). It's also used to quickly move the stack pointer upon exiting a function.
The reason it's done this way is to simplify the code so that you're not having to reference stack content based on a possibly-changing stack pointer. Using the base pointer considerably eases the task of code generation (you don't have to know what the stack pointer is at any given time, just use the base pointer, which remains the same for the duration of the function).
Without that, code that wanted to push two copies of a local variable to call the next function, would look like this:
mov eax, [esp+16] ; get var1 push eax ; push it mov eax, [esp+20] ; get var1 again push eax call _somethingElse
Put aside the fact that you wouldn't reload eax in this case, the point I'm trying to make is that the relative location of items from a moving stack pointer can needlessly complicate matters.
For example, here's a function coded in assembly that follows a common calling convention:
_doSomething: push ebp ; stack current base pointer mov ebp, esp ; save previous stack pointer sub esp, 48 ; incl 48 bytes local storage ; do whatever you want here, including changing ; esp, as long as it ends where it started. mov esp, ebp ; restore previous stack pointer pop ebp ; and previous base pointer ret ; then return _callIt: mov eax, 7 push eax ; push parameter for function call _doSomething add esp, 4 ; get rid of pushed value :
If you follow the code, you can see that ebp inside the function body is a fixed reference point with [ebp] (contents of ebp) being the return address, [ebp+4] being the pushed value of 7, and [ebp-N] being the local storage for _doSomething, where N varies from 1 to 48.
This is the case regardless of how many items are pushed or popped within the body of the function.
alloca()can alter the stackframe dynamically; Thus it becomes useful to have bothespandebp.VLAandalloca()are quite rare situations and I also doubt compilers would produce assembly code containing them. Why would x86 designers waste 1/8 general register to handle this rare case?alloca()& co. (which is the vast majority) usually don't "waste" the register, while the functions that do, do. And yes, compilers do produce the corresponding code if you use VLAs as in, say,int foo(int n){int bar[n]; /* ... */ return bar[0];}or callalloca(). It's difficult to track where's the top of the stackframe otherwise.[bp+disp](or[ss:bx],[ss:si+disp]or[ss:di+disp]) must be used to address the stack, as addressing forms[sp]and[sp+disp]do not exist in x86.[esp]is available in 386 code and[rsp]in x86-64 code. See my answer stackoverflow.com/questions/14881174/… for 16-bit x86 examples in which[bp]is used.