Skip to main content
Fixed the stack picture.
Source Link
Edgar Bonet
  • 45.2k
  • 4
  • 42
  • 81
address | SP = Y | Y+1 | Y+2  | Y+3  | Y+4 | Y+5  |  Y+6  |...
--------------------------------------------------------------
data    | (free) | room for x |  saved Y  | return addr. | ...
  • I passed the pointer to another function just to make gcc believe that I am actually using that pointer, otherwise it would optimize out the whole code into a function that does noting but return.
  • You don't need to create a pointer variable to hold the address of x: calling just bar(&x) compiles into the very same assembly.
  • Most local variablevariables are assigned to CPU registers. Only because I am using the address of x did this variable get stored in the stack.
  • The compiler output is very sensitive to the surrounding code, the compiler version, the optimization options, and so on. So you should consider all this as only an example.
  • If you do not completely understand what each instruction is doing, you should now take a look at the AVR Instruction Set Manual
address | SP = Y | Y+1 | Y+2  | Y+3  | Y+4   | ...
-------------------------------------------------
data    | (free) | room for x | return addr. | ...
  • I passed the pointer to another function just to make gcc believe that I am actually using that pointer, otherwise it would optimize out the whole code into a function that does noting but return.
  • You don't need to create a pointer variable to hold the address of x: calling just bar(&x) compiles into the very same assembly.
  • Most local variable are assigned to CPU registers. Only because I am using the address of x did this variable get stored in the stack.
  • The compiler output is very sensitive to the surrounding code, the compiler version, the optimization options, and so on. So you should consider all this as only an example.
  • If you do not completely understand what each instruction is doing, you should now take a look at the AVR Instruction Set Manual
address | SP = Y | Y+1 | Y+2  | Y+3 | Y+4 | Y+5  |  Y+6  |...
--------------------------------------------------------------
data    | (free) | room for x |  saved Y  | return addr. | ...
  • I passed the pointer to another function just to make gcc believe that I am actually using that pointer, otherwise it would optimize out the whole code into a function that does noting but return.
  • You don't need to create a pointer variable to hold the address of x: calling just bar(&x) compiles into the very same assembly.
  • Most local variables are assigned to CPU registers. Only because I am using the address of x did this variable get stored in the stack.
  • The compiler output is very sensitive to the surrounding code, the compiler version, the optimization options, and so on. So you should consider all this as only an example.
  • If you do not completely understand what each instruction is doing, you should now take a look at the AVR Instruction Set Manual
Source Link
Edgar Bonet
  • 45.2k
  • 4
  • 42
  • 81

Just as an experiment, I tried the following:

void bar(int *);

void foo()
{
    int x = 5;
    int *p;
    p = &x;
    bar(p);
}

And here is what gcc translated that into (comments mine):

__SP_H__ = 0x3e
__SP_L__ = 0x3d
__tmp_reg__ = 0
foo:
    push r28            ; save r28
    push r29            ; save r29
    rcall .             ; make room on the stack
    in   r28, __SP_L__  ; copy SP into r29:r28 = Y: LSB...
    in   r29, __SP_H__  ; ... and MSB
    ldi  r24, lo8(5)    ; store x in a register: LSB...
    ldi  r25, 0         ; ... and MSB
    std  Y+2, r25       ; copy x into the stack: MSB
    std  Y+1, r24       ; ... and LSB
    movw r24, r28       ; r25:r24 = Y
    adiw r24, 1         ; r25:r24 = Y+1 = &x
    call bar            ; call bar(&x);
    pop  __tmp_reg__    ; release the two bytes...
    pop  __tmp_reg__    ; ... of stack space
    pop  r29            ; restore r29
    pop  r28            ; restore r28
    ret                 ; return

Expanded comments:

The instruction rcall is normally used to call another function. Here it's calling the following instruction, so it doesn't affect the program flow. It's only a trick: as rcall saves the return address into the stack, it makes the stack grow by two bytes, and is used here as an optimized way of reserving to bytes of stack space for storing x.

Next you see the stack pointer being copied into the r29:r28 register pair, also known as “Y pointer”. The stack pointer is a CPU register holding the address of the top of the stack, namely of the first free slot. As the stack grows towards the bottom of the RAM, at this point it looks like this:

address | SP = Y | Y+1 | Y+2  | Y+3  | Y+4   | ...
-------------------------------------------------
data    | (free) | room for x | return addr. | ...

Next you see the value of x being placed first in a register pair (r25:r24), then on the stack at the addresses Y+1 and Y+2.

The instruction mowv copies a register pair, so now you have a copy of the stack pointer in the pair r25:r24. The instruction adiw adds an immediate value (here, 1) to a register pair, so that now r25:r24 holds the address of x. Finally, the function bar() is called: per the calling convention, the argument is to be passed in the r25:r24 pair.

A few final remarks:

  • I passed the pointer to another function just to make gcc believe that I am actually using that pointer, otherwise it would optimize out the whole code into a function that does noting but return.
  • You don't need to create a pointer variable to hold the address of x: calling just bar(&x) compiles into the very same assembly.
  • Most local variable are assigned to CPU registers. Only because I am using the address of x did this variable get stored in the stack.
  • The compiler output is very sensitive to the surrounding code, the compiler version, the optimization options, and so on. So you should consider all this as only an example.
  • If you do not completely understand what each instruction is doing, you should now take a look at the AVR Instruction Set Manual