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.
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;
}