开发者

how do static variables inside functions work?

开发者 https://www.devze.com 2023-04-12 06:50 出处:网络
In the following code: int count(){ static int n(5); n = n + 1; return n; } the variable n is instantiated only once at the first call to the function.

In the following code:

int count(){
    static int n(5);
    n = n + 1;
    return n;
}

the variable n is instantiated only once at the first call to the function.

There should be a flag or something so it initialize the variable only once.. I tried to look on th开发者_高级运维e generated assembly code from gcc, but didn't have any clue.

How does the compiler handle this?


This is, of course, compiler-specific.

The reason you didn't see any checks in the generated assembly is that, since n is an int variable, g++ simply treats it as a global variable pre-initialized to 5.

Let's see what happens if we do the same with a std::string:

#include <string>

void count() {
    static std::string str;
    str += ' ';
}

The generated assembly goes like this:

_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

The line I've marked with the bypass initialization comment is the conditional jump instruction that skips the construction if the variable already points to a valid object.


This is entirely up to the implementation; the language standard says nothing about that.

In practice, the compiler will usually include a hidden flag variable somewhere that indicates whether the static variable has already been instantiated or not. The static variable and the flag will probably be in the static storage area of the program (e.g. the data segment, not the stack segment), not in the function scope memory, so you may have to look around about in the assembly. (The variable can't go on the call stack, for obvious reasons, so it's really like a global variable. "static allocation" really covers all sorts of static variables!)

Update: As @aix points out, if the static variable is initialized to a constant expression, you may not even need a flag, because the initialization can be performed at load time rather than at the first function call. In C++11 you should be able to take advantage of that better than in C++03 thanks to the wider availability of constant expressions.


It's quite likely that this variable will be handled just as ordinary global variable by gcc. That means the initialization will be statically initialized directly in the binary.

This is possible, since you initialize it by a constant. If you initialized it eg. with another function return value, the compiler would add a flag and skip the initialization based on the flag.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号