How static modifier works in C?

134 views Asked by At

I am trying to understand how the "static" modifier works in C, I went looking for its meaning and everything I found seemed a bit vague.

It is a modifier to allow the values of a variable to exist until the end of the program execution.

I understood what it means and its purpose, but beyond this definition I wanted to understand how it works underneath, so I generated the assembly of the C code

char    *thing(char *a)
{
    char *b;

    b = malloc(3);

    b[0] = 'y';
    b[1] = '\0';
    return (b);
}

char    *some(int fd)
{
    static char *a = "happened";
    a = thing(a);
    return (a);
}

I create another code with non-static a variable and got this

/* With static variable */
    .file   "static_test.c"
    .text
    .globl  thing
    .type   thing, @function
thing:
.LFB6:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movq    %rdi, -24(%rbp)
    movl    $3, %edi
    call    malloc@PLT
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movb    $121, (%rax)
    movq    -8(%rbp), %rax
    addq    $1, %rax
    movb    $0, (%rax)
    movq    -8(%rbp), %rax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE6:
    .size   thing, .-thing
    .globl  some
    .type   some, @function
some:
.LFB7:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movq    a.0(%rip), %rax
    movq    %rax, %rdi
    call    thing
    movq    %rax, a.0(%rip)
    movq    a.0(%rip), %rax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE7:
    .size   some, .-some
    .section    .rodata
.LC0:
    .string "happened"
    .section    .data.rel.local,"aw"
    .align 8
    .type   a.0, @object
    .size   a.0, 8
a.0:
    .quad   .LC0
    .ident  "GCC: (GNU) 12.1.0"
    .section    .note.GNU-stack,"",@progbits

/* no static variable */
    .file   "nostatic_test.c"
    .text
    .globl  thing
    .type   thing, @function
thing:
.LFB6:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movq    %rdi, -24(%rbp)
    movl    $3, %edi
    call    malloc@PLT
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movb    $121, (%rax)
    movq    -8(%rbp), %rax
    addq    $1, %rax
    movb    $0, (%rax)
    movq    -8(%rbp), %rax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE6:
    .size   thing, .-thing
    .section    .rodata
.LC0:
    .string "happened"
    .text
    .globl  some
    .type   some, @function
some:
.LFB7:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movl    %edi, -20(%rbp)
    leaq    .LC0(%rip), %rax
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    %rax, %rdi
    call    thing
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE7:
    .size   some, .-some
    .ident  "GCC: (GNU) 12.1.0"
    .section    .note.GNU-stack,"",@progbits

The question would be, what is happening and the difference between the two assembly codes and how does this behave at compile time and at program execution time.

2

There are 2 answers

3
rcgldr On BEST ANSWER

This might be a better example. r has local scope, but it will not be located locally on the stack, but either in the .bss or .data section of a program and only initialized one time to zero. After that each call to Rnd32 will update r. The program returns a pseudo random 32 bit unsigned integer on each call in a fixed order, so that it is a repeatable sequence that goes through all 2^32 possible values.

uint32_t Rnd32()
{
static uint32_t r = 0;
    r = r*1664525 + 1013904223;
    return r;
}
0
Erik Eidt On

In the static version, you have a pointer-sized datum reserved in the .data section, which is initialized by the program file/image to refer to the string literal (which is in the .rodata section).  This is the

a.0:
   .quad   .LC0

In the non-static version, the variable is "automatic" — a local variable of the function, and is created on function entry and effectively destroyed on function exit.  Because the variable comes into existence each time the function is called, it must be initialized each time.  In the code you're showing (unoptimized) this automatic variable lives on the stack.  (Optimization would improve the code.)

Static variables can enjoy an efficiency with respect to such initialization, whereas local variable enjoy the performance possibility of living in a CPU register (fast access & taking no memory), and potentially being recursion and thread safe by contrast with statics.

As you note, also the static variable lives on after the function stops because it has global storage and the compiler knows it how to access it.  The automatic/stack-based variable, by contrast, is lost after returning from the function — more specifically, the variable is re-created each time the function is called, so the old copy is no longer (realistically) accessible.  You could make a pointer to the automatic variable, but it would be a logic error to use/dereference that pointer after the function exits.