Your understanding is not correct. You do not have to do any pushing or popping to access different variables. As far as your source code is concerned, there's no awareness of a stack or any stack operations. You just reference the variables as you need to:
z = x * y;
First, C does not mandate the use of a stack to store local (auto) variables; that's an implementation detail. Almost every C implementation does use a stack for this purpose, but it's not required.
Secondly, for implementations that use a stack, individual items are not pushed or popped as they are accessed. Instead, when a function is called a whole region of the stack, usually called a stack frame, is set aside and the function's arguments and local variables are referred to via an offset from an address within that frame.
For example, assume the function
void foo( int x, int y )
{
int a, b;
// some code
}
On an x86-like system, when this function is called a region of space on the stack will be allocated to store the function arguments, the local variables, the return address (i.e., the address of the next instruction to execute after the function returns), and the address of the previous stack frame (which stores the state of whatever function called foo). It would look something similar to this:
+-----------------+
0x80000000: | y | Argument 2
+-----------------+
0x7FFFFFFC: | x | Argument 1
+-----------------+
0x7FFFFFF8: | 0x01234567 | Address of next instruction
+-----------------+
0x7FFFFFF4: | 0x80000010 | Address of previous stack frame <--+
+-----------------+ |
0x7FFFFFF0: | a | Local variable |
+-----------------+ |
0x7FFFFFDC: | b | Local variable <-+ |
+-----------------+ | |
| |
+-----------------+ | |
esp: | 0x7FFFFFC8 | Stack pointer ---+ |
+-----------------+ |
|
+-----------------+ |
ebp: | 0x7FFFFFF4 | Frame pointer ---------------------+
+-----------------+
(This assumes 32-bit integers and addresses, and the addresses shown are only for illustration)
There are two registers that are used to keep track of the runtime stack. The stack pointer (esp or rsp on x86 and x86-64 platforms) stores the address of the last item pushed on the stack. On x86 the stack grows "downward" from high to low address, so the "top" of the stack has a lower address than the "bottom" of the stack. The frame pointer (ebp or rbp on x86 and x86-64) stores the address of the current stack frame. Function arguments and local variables are referenced via offsets from the frame pointer. So if you write something like
a = 10;
the compiled machine code does something like
mov 0x0c,-0x04(%rpb) ;; write 10 to the location 4 bytes "below" the address
;; stored in rbp, or 0x7FFFFFF0
Function argument x would be 8 bytes "above" the frame pointer, or 0x08(%rbp) (0x7FFFFFFC).
There's no requirement that local variables be allocated in any specific order - the compiler is free to arrange the space for them however it sees fit. Similarly, there's no requirement that function arguments be pushed in any specific order (or that they be pushed on the stack at all), but almost all systems that use a stack for function arguments expect them to be pushed in reverse order.