В С++, являются ли статические инициализации примитивных типов постоянными значениями потокобезопасными?

i.e, можно ли ожидать, что следующее будет выполняться корректно даже в многопоточной среде?

int dostuff(void) {
    static int somevalue = 12345;
    return somevalue;
}

Или это возможно для того, чтобы несколько потоков вызывали это, и один вызов, чтобы вернуть все мусор в &somevalue до начала выполнения?

Ответ 1

Из стандарта С++, раздел 6.7:

Локальный объект типа POD (3.9) со статической продолжительностью хранения инициализируется постоянными выражениями инициализируется до того, как его блок сначала введен.

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

Ответ 2

В разделе 6.7 стандарта говорится следующее:

Нулевая инициализация всех локальных объекты со статической продолжительностью хранения выполняется перед любым другим происходит инициализация. Местный объект типа POD со статическим хранилищем длительность, инициализированная инициализируются константные выражения прежде чем его блок будет введен первым. выполнение разрешено выполнять ранняя инициализация других локальных объекты со статической продолжительностью хранения при тех же условиях, что осуществление разрешено статически инициализировать объект с помощью длительность статического хранения в пространстве имен объем. В противном случае такой объект инициализировал первый контроль времени проходит через его декларацию; такие объект считается инициализированным по завершении инициализация. Если инициализация выходы путем исключения исключения, инициализация не завершена, поэтому снова будут проверены в следующий раз контроль вводит декларацию. Если контроль возвращается в декларацию (рекурсивно), пока объект инициализируется, поведение undefined.

Итак, если это тип POD, то, похоже, инициализация происходит при запуске, прежде чем начнутся новые потоки. Для не-POD-типов это сложнее, стандарт говорит, что поведение undefined (если в другом месте ничего не говорится о безопасности потока во время инициализации).

Мне известно, что при инициализации объекта, отличного от POD, GCC захватывает мьютекс, чтобы предотвратить его инициализацию дважды (я знаю это, потому что однажды отключил программу, случайно рекурсивную инициализацию статического объекта).

К сожалению, я не могу сказать вам, если это относится к другим компиляторам, или это предусмотрено в других стандартах.

Ответ 3

Да, он полностью безопасен (на большинстве компиляторов). Я бы рекомендовал бросить точку перерыва и посмотреть, как выполняется задание в вашем конкретном компиляторе. Я не могу сказать, сколько раз нарушаются "стандарты".

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

В g++ для OS X 10.6.2 это машинный код, сгенерированный для вашей функции:

push   rbp
mov    rbp,rsp
lea    rax,[rip+0x2067]        # 0x100003170 <_ZZ7dostuffvE9somevalue>
mov    eax,DWORD PTR [rax]
leave  
ret

Как вы можете видеть, нет назначения. Компилятор испекли примитив во время сборки.

Ответ 4

Поскольку для инициализатора somevalue не требуется вызов конструктора, это будет работать нормально (некоторое значение будет инициализировано во время сборки).

Теперь, если вы инициализировали значение, требующее конструктора:

void whatever()
{
    static std::string value("bad");

    ...
}

Затем вы можете столкнуться с проблемами с несколькими потоками. Внутри это превратится в нечто вроде:

void whatever()
{
    static bool value_initialized = false;
    static string_struct value;

    if (!initialized)
    {
        construct_string(&value, "bad");
        value_initialized = false;
    }

    ....
 }

При наличии нескольких потоков у вас есть различные проблемы, в том числе условия гонки и видимость в памяти).

Ответ 5

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

Объем файла один безопасно инициализирован до того, как все потоки пройдут, а область действия - нет. Это одно из немногих мест, где вы не можете придерживаться правила минимального объема.

Обратите внимание, что это, похоже, зависит от версий компилятора (чего вы ожидаете, учитывая, что мы ходим в областях поведения undefined)