函数内部的静态变量是如何工作的
how do static variables inside functions work?
在以下代码中:
int count(){
static int n(5);
n = n + 1;
return n;
}
变量n
只在第一次调用函数时实例化一次。
应该有一个标志之类的东西,这样它只初始化变量一次。我试图查看从gcc生成的汇编代码,但没有任何线索。
编译器如何处理这个?
当然,这是特定于编译器的。
您在生成的程序集中没有看到任何检查的原因是,由于n
是int
变量,g++
只是将其视为预初始化为5的全局变量。
让我们看看如果我们对std::string
:
#include <string>
void count() {
static std::string str;
str += ' ';
}
生成的程序集如下所示:
_Z5countv:
.LFB544:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA544
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
pushq %r13
pushq %r12
pushq %rbx
subq $8, %rsp
movl $_ZGVZ5countvE3str, %eax
movzbl (%rax), %eax
testb %al, %al
jne .L2 ; <======= bypass initialization
.cfi_offset 3, -40
.cfi_offset 12, -32
.cfi_offset 13, -24
movl $_ZGVZ5countvE3str, %edi
call __cxa_guard_acquire ; acquire the lock
testl %eax, %eax
setne %al
testb %al, %al
je .L2 ; check again
movl $0, %ebx
movl $_ZZ5countvE3str, %edi
.LEHB0:
call _ZNSsC1Ev ; call the constructor
.LEHE0:
movl $_ZGVZ5countvE3str, %edi
call __cxa_guard_release ; release the lock
movl $_ZNSsD1Ev, %eax
movl $__dso_handle, %edx
movl $_ZZ5countvE3str, %esi
movq %rax, %rdi
call __cxa_atexit ; schedule the destructor to be called at exit
jmp .L2
.L7:
.L3:
movl %edx, %r12d
movq %rax, %r13
testb %bl, %bl
jne .L5
.L4:
movl $_ZGVZ5countvE3str, %edi
call __cxa_guard_abort
.L5:
movq %r13, %rax
movslq %r12d,%rdx
movq %rax, %rdi
.LEHB1:
call _Unwind_Resume
.L2:
movl $32, %esi
movl $_ZZ5countvE3str, %edi
call _ZNSspLEc
.LEHE1:
addq $8, %rsp
popq %rbx
popq %r12
popq %r13
leave
ret
.cfi_endproc
我用bypass initialization
注释标记的行是条件跳转指令,如果变量已经指向有效对象,则跳过构造。
这完全取决于实现;语言标准对此没有规定。
实际上,编译器通常会在某个地方包含一个隐藏的标志变量,用于指示静态变量是否已经实例化。静态变量和标志可能在程序的静态存储区域(例如数据段,而不是堆栈段),而不是在函数作用域内存中,所以你可能需要在程序集中查找。(由于显而易见的原因,该变量不能进入调用堆栈,因此它实际上就像一个全局变量。"静态分配"真的涵盖了所有类型的静态变量!)
Update:正如@aix所指出的,如果静态变量初始化为常量表达式,您甚至可能不需要标记,因为初始化可以在加载时执行,而不是在第一次函数调用时执行。在c++ 11中,由于常量表达式的广泛可用性,您应该能够比c++ 03更好地利用这一点。
这个变量很可能会被gcc当作普通的全局变量来处理。这意味着初始化将在二进制文件中直接静态初始化。
这是可能的,因为你用一个常量初始化它。如果你初始化它。对于另一个函数返回值,编译器将添加一个标志,并跳过基于该标志的初始化。
相关文章:
- QSqlquery prepare()和bindvalue()不工作
- 导入库可以跨dll版本工作吗
- 以螺旋方式打印矩阵的程序.(工作不好)
- 对象指针在c++中是如何工作的
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- VSOMEIP-2个设备之间的通信(TCP/UDP)不工作
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- C++为线程工作动态地分割例程
- 为什么我的 std::ref 无法按预期工作?
- 布尔比较运算符是如何在C++中工作的
- SampleConsensusPrerejective(ext.RANSAC)是如何真正工作的
- 不确定要在我的main中放入什么才能使我的代码正常工作
- 为什么std::condition_variable notify_all的工作速度比notify_one快(对于随机请
- <<操作员在下面的行中工作
- 有人能解释一下为什么下界是这样工作的吗C++的
- ExtractIconEx:可以工作,但偶尔会崩溃
- C++中的memset函数工作不正常
- 当我在第一个循环中使用"auto"时,它工作正常,但是使用"int"它会给出错误,为什么?
- 当 int 方法工作正常时,void 方法有何不同,或者为什么我不能调用 void 方法?
- sdl软件渲染器不工作,工作在硬件加速的一个