Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how do static variables inside functions work?

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 the generated assembly code from gcc, but didn't have any clue.

How does the compiler handle this?

like image 739
Yousf Avatar asked Oct 12 '11 13:10

Yousf


People also ask

How do you use a static variable in a function?

A static variable is a variable that is declared using the keyword static. The space for the static variable is allocated only one time and this is used for the entirety of the program. Once this variable is declared, it exists till the program executes.

Can static variable be declared inside function?

i.e. you cannot use a local variable outside the current method which contradicts with the definition of class/static variable. Therefore, declaring a static variable inside a method makes no sense, if you still try to do so, a compile time error will be generated.

Does static apply to functions?

A static function in C is a function that has a scope that is limited to its object file. This means that the static function is only visible in its object file. A function can be declared as static function by placing the static keyword before the function name.

What are static variables and what are functions?

Static functions can be called directly by using class name. Static variables are initialized only once. Compiler persist the variable till the end of the program. Static variable can be defined inside or outside the function. They are local to the block.


3 Answers

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.

like image 89
NPE Avatar answered Sep 23 '22 08:09

NPE


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.

like image 22
Kerrek SB Avatar answered Sep 25 '22 08:09

Kerrek SB


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.

like image 31
jpalecek Avatar answered Sep 23 '22 08:09

jpalecek