Подтвердить что ты не робот

Как работают статические переменные внутри функций?

В следующем коде:

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

переменная n создается только один раз при первом вызове функции.

Должен быть флаг или что-то такое, чтобы он инициализировал переменную только один раз. Я попытался просмотреть сгенерированный код сборки из gcc, но не имел никакой подсказки.

Как компилятор справляется с этим?

4b9b3361

Ответ 1

Это, конечно, специфичный для компилятора.

Причина, по которой вы не видели никаких проверок в сгенерированной сборке, заключается в том, что поскольку 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, является инструкция условного перехода, которая пропускает конструкцию, если переменная уже указывает на действительный объект.

Ответ 2

Это полностью зависит от реализации; языковой стандарт ничего не говорит об этом.

На практике компилятор обычно включает в себя скрытую флаговую переменную где-нибудь, которая указывает, была ли статическая переменная уже создана или нет. Статическая переменная и флаг, вероятно, будут находиться в области статического хранения программы (например, сегмент данных, а не сегмент стека), а не в памяти области функций, поэтому вам может потребоваться осмотр в сборке. (Переменная не может идти в стек вызовов по понятным причинам, поэтому она действительно похожа на глобальную переменную. "Static allocation" действительно охватывает всевозможные статические переменные!)

Обновление. Поскольку @aix указывает, если статическая переменная инициализируется константным выражением, вам может даже не понадобиться флаг, потому что инициализация может выполняться во время загрузки, а не при первом вызове функции. В С++ 11 вы сможете использовать это лучше, чем в С++ 03 благодаря более широкой доступности постоянных выражений.

Ответ 3

Весьма вероятно, что эта переменная будет обрабатываться как обычная глобальная переменная gcc. Это означает, что инициализация будет статически инициализирована непосредственно в двоичном формате.

Это возможно, поскольку вы инициализируете его константой. Если вы его инициализировали, например. с другим возвращаемым значением функции, компилятор добавит флаг и пропустит инициализацию на основе флага.