как работают статические переменные внутри функций? - PullRequest
15 голосов
/ 12 октября 2011

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

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

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

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

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

Ответы [ 3 ]

17 голосов
/ 12 октября 2011

Это, конечно, зависит от компилятора.

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

7 голосов
/ 12 октября 2011

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

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

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

4 голосов
/ 12 октября 2011

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

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

...