1

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
}

And:

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?)

David542
  • 104,438
  • 178
  • 489
  • 842
  • With my minor knowledge of assembly, it would seem like a static variable would be allocated in the `.data` section and would persist there, whereas a non-static variable would be allocated on the stack and its life would depend on the allocation/deallocation of the stack frame that contains it. – Carcigenicate Aug 29 '20 at 00:14
  • @Carcigenicate yea I was thinking the same about any automatic variable going onto the stack, but not too sure about the `static` way. And also, how closures might be handled and such... – David542 Aug 29 '20 at 00:15
  • @Carcigenicate Uninitialized `static` (or `static` initialized to 0) would go to the `.bss` section. – user58697 Aug 29 '20 at 00:25
  • non-static local variables can live purely in a register, if their address isn't taken and used. Otherwise reserve stack space. static locals go in .data, .rodata, or .bss just like globals. Have a look at how these compile with `-O2` vs. `-O0` (see [How to remove "noise" from GCC/clang assembly output?](https://stackoverflow.com/q/38552116)) – Peter Cordes Aug 29 '20 at 00:27

3 Answers3

3

To implement a static object in assembly, you define it in a data section (of which there are various types, involving options for initialization and modification).

To implement an automatic object in assembly, you include space for it in the stack frame of a routine.

Examples, not necessarily syntactically correct in a particular assembly language, might be:

        .data
foo:    .word 34  // Static object named "foo".

        .text
…
        lr r3, foo // Load value of foo.

and:

        .text
bar:                    // Start of routine named "bar".
foo = 16                // Define a symbol for convenience.
        add sp, sp, -CalculatedSize // Allocate stack space for local data.
…
        li  r3, 34      // Load immediate value into register.
        sr  r3, foo(sp) // Store value into space reserved for foo on stack.
…
        add sp, sp, +CalculatedSize // Automatic objects are released here.
        ret

These are very simplified examples (as requested). Many modern schemes for using the hardware stack include frame pointers, which are not included above.

In the second example, CalculatedSize represents some amount that includes space for registers to be saved, space for the foo object, space for arguments for subroutine calls, and whatever other stack space is needed by the routine. The offset of 16 provided for foo is part of those calculations; the author of the routine would arrange their stack frame largely as they desire.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
2

Just try it

void more_fun ( int );
void fun0 ( void )
{
    for (int i = 0; i < 500; ++i) {
        static int n = 0;
        more_fun(++n);
    }
}
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.

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • great thanks for such a detailed example and walk-through. Could you please show the commands you used to compile/disassemble? – David542 Aug 29 '20 at 00:44
  • arm-linux-gnueabi-gcc -O2 -c so.c -o so.o ; arm-linux-gnueabi-gcc-objdump -D. by replacing printf from yours to something generic any arm-whatever-whatever-variant will work, can use whatever other compiler gcc -O2 mips-whatever-gcc -O2, etc...arm disassembles well and is simple and easy to read. – old_timer Aug 29 '20 at 13:11
-5

When modifying a variable, the static local variable modified by static is executed only once, and the life cycle of the local variable is extended until the program is run. If you don't add static, each loop reallocates the value.

suchenguo
  • 4
  • 1
  • This does not appear to answer the question. – Carcigenicate Aug 29 '20 at 00:13
  • 1
    I am not sure it answers anything. Variables are not executed. The lifetime of an object does not start until a program is run; there is nothing to extend until a program is run. There is no meaning to the “opposite” of executing a variable only once. Alternatives include zero times or more than once, but which is the opposite? Neither would be correct; when a variable is modified, it is modified once, not zero times, not twice, so what is the point of that first sentence? What is the opposite of extending the “life cycle” of a variable? Shortening it? – Eric Postpischil Aug 29 '20 at 00:18