Возникает ли локальная статическая переменная функции автоматически на ветку?

Например:

int foo()
{
    static int i = 0;
    return i++;
}

Переменная i будет инициализирована только на 0 при первом вызове foo. Это автоматически означает, что там есть скрытая ветвь, чтобы повторить инициализацию более одного раза? Или есть более умные трюки, чтобы избежать этого?

Ответ 1

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

Реализация может только уклониться от этого требования, если это может доказать, что разница между ленивым инициализацией и некоторой более ранней инициализацией, как и до ввода main(), эквивалентна. Например, простые POD, инициализированные из констант, компилятор может захотеть инициализировать его раньше, чем глобальный файл-область, поскольку он не наблюдается и сохраняет ленивый код инициализации, но это не наблюдаемая оптимизация.

Ответ 2

Да, есть ветка. Каждый раз, когда функция вводится, код должен проверить, была ли уже инициализирована переменная. Но, как будет объяснено ниже, вам обычно не нужно заботиться об этой ветке.

Пример

Проверьте этот код:

#include <iostream>

struct Foo { Foo(){ std::cout << "FOO" << std::endl;} };
void foo(){ static Foo foo; }
int main(){ foo();}

Теперь вот первая часть кода сборки, которую gcc4.8 генерирует для функции foo:

_Z3foov:
.LFB974:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA974
pushq   %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq    %rsp, %rbp
.cfi_def_cfa_register 6
pushq   %r12
pushq   %rbx
.cfi_offset 12, -24
.cfi_offset 3, -32
movl    $_ZGVZ3foovE3foo, %eax
movzbl  (%rax), %eax
testb   %al, %al
jne .L7                     <------------------- FIRST CHECK
movl    $_ZGVZ3foovE3foo, %edi
call    __cxa_guard_acquire <------------------- LOCK    
testl   %eax, %eax
setne   %al
testb   %al, %al
je  .L7                     <------------------- SECOND CHECK
movl    $0, %r12d
movl    $_ZZ3foovE3foo, %edi

Вы видите, есть jne! Затем охранник приобретается с помощью __cxa_guard_acquire, а затем je. Таким образом, кажется, что компилятор генерирует знаменитый двойной проверенный шаблон блокировки здесь.

Будет ли каждый компилятор сгенерировать ветвь?

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

Является ли ветка дорогой?

Учитывая, следует ли вам заботиться об этой ветке: Вы должны определенно НЕ заботиться об этой ветке, так как она будет правильно предсказана (поскольку она, как только объект инициализируется, ветвь всегда берет тот же маршрут). Таким образом, ветвь почти свободна. Попытка избежать статической локальной переменной для целей оптимизации никогда не должна приводить к заметным преимуществам производительности.

На самом деле нет пути вокруг ветки?

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

Единственная возможность, о которой я вижу, изложена в ответе Р. Мартиньо Фернандеса (который был удален): код мог бы модифицировать себя. I.e., просто удалите код инициализации после завершения инициализации. Однако это идея непрактична по следующим причинам:

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