Example Of A Static Vs Automatic Variable In Assembly

- 1 answer

In C, we can use the following two examples to show the difference between a static and non-static variable:

for (int i = 0; i < 5; ++i) {
    static int n = 0;
    printf("%d ", ++n);  // prints 1 2 3 4 5  - the value persists


for (int i = 0; i < 5; ++i) {
    int n = 0;
    printf("%d ", ++n);  // prints 1 1 1 1 1  - the previous value is lost

Source: this answer.

What would be the most basic example in assembly to show the difference between how a static or non-static variable is created? (Or does this concept not exist in assembly?)



Just try it

void more_fun ( int );
void fun0 ( void )
    for (int i = 0; i < 500; ++i) {
        static int n = 0;
void fun1 ( void )
    for (int i = 0; i < 500; ++i) {
        int n = 0;
        more_fun( ++n);

Disassembly of section .text:

00000000 <fun0>:
   0:   e92d4070    push    {r4, r5, r6, lr}
   4:   e3a04f7d    mov r4, #500    ; 0x1f4
   8:   e59f501c    ldr r5, [pc, #28]   ; 2c <fun0+0x2c>
   c:   e5953000    ldr r3, [r5]
  10:   e2833001    add r3, r3, #1
  14:   e1a00003    mov r0, r3
  18:   e5853000    str r3, [r5]
  1c:   ebfffffe    bl  0 <more_fun>
  20:   e2544001    subs    r4, r4, #1
  24:   1afffff8    bne c <fun0+0xc>
  28:   e8bd8070    pop {r4, r5, r6, pc}
  2c:   00000000

00000030 <fun1>:
  30:   e92d4010    push    {r4, lr}
  34:   e3a04f7d    mov r4, #500    ; 0x1f4
  38:   e3a00001    mov r0, #1
  3c:   ebfffffe    bl  0 <more_fun>
  40:   e2544001    subs    r4, r4, #1
  44:   1afffffb    bne 38 <fun1+0x8>
  48:   e8bd8010    pop {r4, pc}

Disassembly of section .bss:

00000000 <n.4158>:
   0:   00000000    andeq   r0, r0, r0

I like to think of static locals as local globals. They sit in .bss or .data just like globals. But from a C perspective they can only be accessed within the function/context that they were created in.

I local variable has no need for long term storage, so it is "created" and destroyed within that fuction. If we were to not optimize you would see that some stack space is allocated.

00000064 <fun1>:
  64:   e92d4800    push    {fp, lr}
  68:   e28db004    add fp, sp, #4
  6c:   e24dd008    sub sp, sp, #8
  70:   e3a03000    mov r3, #0
  74:   e50b300c    str r3, [fp, #-12]
  78:   ea000009    b   a4 <fun1+0x40>
  7c:   e3a03000    mov r3, #0
  80:   e50b3008    str r3, [fp, #-8]
  84:   e51b3008    ldr r3, [fp, #-8]
  88:   e2833001    add r3, r3, #1
  8c:   e50b3008    str r3, [fp, #-8]
  90:   e51b0008    ldr r0, [fp, #-8]
  94:   ebfffffe    bl  0 <more_fun>
  98:   e51b300c    ldr r3, [fp, #-12]
  9c:   e2833001    add r3, r3, #1
  a0:   e50b300c    str r3, [fp, #-12]
  a4:   e51b300c    ldr r3, [fp, #-12]
  a8:   e3530f7d    cmp r3, #500    ; 0x1f4
  ac:   bafffff2    blt 7c <fun1+0x18>
  b0:   e1a00000    nop         ; (mov r0, r0)
  b4:   e24bd004    sub sp, fp, #4
  b8:   e8bd8800    pop {fp, pc}

But optimized for fun1 the local variable is kept in a register, faster than keeping on the stack, in this solution they save the upstream value held in r4 so that r4 can be used to hold n within this function, when the function returns there is no more need for n per the rules of the language.

For the static local, per the rules of the language that value remains static outside the function and can be accessed within. Because it is initialized to 0 it lives in .bss not .data (gcc, and many others). In the code above the linker will fill this value

  2c:   00000000

in with the address to this

00000000 <n.4158>:
   0:   00000000    andeq   r0, r0, r0

IMO one could argue the implementation didnt need to treat it like a volatile and sample and save n every loop. Could have basically implemented it like the second function, but sampled up front from memory and saved it in the end. Either way you can see the difference in an implementation of the high level code. The non-static local only lives within the function and then its storage anc contents are essentially gone.