我试图了解"static"
修饰语在C中的工作方式,我去寻找它的含义,我发现的一切似乎有点模糊。
这是一个修饰符,允许变量的值一直存在到程序执行结束。
我理解它的含义和目的,但除了这个定义之外,我想了解它是如何工作的,所以我生成了C代码的汇编
char *thing(char *a)
{
char *b;
b = malloc(3);
b[0] = 'y';
b[1] = ' ';
return (b);
}
char *some(int fd)
{
static char *a = "happened";
a = thing(a);
return (a);
}
我用非静态a
变量创建了另一个代码,得到了这个
/* 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
问题是,这两个汇编代码之间发生了什么,有什么不同,在编译时和程序执行时是如何表现的。
在静态版本中,您在.data
部分保留了一个指针大小的数据,该数据由程序文件/图像初始化以引用字符串文字(在.rodata
部分中)。这是
a.0:
.quad .LC0
在非静态版本中,变量是"automatic"-函数的局部变量,在函数入口时创建,在函数退出时销毁。因为变量在每次调用函数时都存在,所以每次都必须初始化它。在您显示的代码中(未优化),这个自动变量存在于堆栈中。(优化将改进代码。)
静态变量可以享受这种初始化的效率,而局部变量可以享受驻留在CPU寄存器中的性能可能性(快速访问&不占用内存),并且与静态量相比,可能是递归和线程安全的。
正如你所注意到的,静态变量在函数停止后仍然存在,因为它具有全局存储并且编译器知道如何访问它。相比之下,基于自动/堆栈的变量在从函数返回后丢失——更具体地说,每次调用函数时都会重新创建变量,因此旧的副本不再(实际上)可访问。您可以创建指向自动变量的指针,但在函数退出后使用/解引用该指针将是一个逻辑错误。
这可能是一个更好的例子。r
具有局部作用域,但它不会位于堆栈的局部,而是位于程序的.bss或.data节中,并且只初始化一次为零。之后,每次调用Rnd32将更新r
。程序在每次调用时以固定顺序返回一个伪随机32位无符号整数,因此它是一个可重复的序列,遍历所有2^32个可能的值。
uint32_t Rnd32()
{
static uint32_t r = 0;
r = r*1664525 + 1013904223;
return r;
}