Как MSVC оптимизирует использование статических переменных?

Мне интересно, как компилятор Microsoft Visual С++ обрабатывает/оптимизирует статические переменные.

Мой код:

#include <cstdlib>

void no_static_initialization()
{
    static int value = 3;
}

void static_initialization(int new_value)
{
    static int value = new_value;
}

int main()
{
    no_static_initialization();
    static_initialization(1);
    static_initialization(std::rand());

    return 0;
}

Здесь сборка для кода (скомпилирована с оптимизацией):

picture of the assembly listing

Моя основная область интересов - последний случай.

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

Каждый из них выполняет test something something, а затем делает короткий jump, если тест не был успешным (эти прыжки, очевидно, указывают на конец соответствующей процедуры).

Является ли компилятор явной проверкой для каждого вызова функции, если функция вызывается в первый раз?
Есть ли у компилятора flag, который указывает, является ли это впервые вызовом функции или нет?
Где он хранится (я думаю, что все, что test материал об этом, но я не совсем уверен)?

Ответы

Ответ 1

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

Флаг, по-видимому, находится по адресу 0x00403374 и берет байт, а сама переменная находится по адресу 0x00403370.

Ответ 2

Мне нравится использовать LLVM, потому что генерируемый им код говорит вам более явно, что он делает:

Фактический код ниже, потому что он долго читается. Да, LLVM создает защитные условия для статических значений. обратите внимание, как static_initialization/bb: получает охрану, проверяет, соответствует ли это определенному значению, уже инициализированному, и либо веткится на bb1, если требуется инициализировать, либо bb2, если это не так. Это не единственный способ решить одно требование инициализации, но это обычный способ.

; ModuleID = '/tmp/webcompile/_31867_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-linux-gnu"

@guard variable for static_initialization(int)::value = internal global i64 0 ; <i64*> [#uses=3]
@static_initialization(int)::value = internal global i32 0 ; <i32*> [#uses=1]

define void @no_static_initialization()() nounwind {
entry:
  br label %return

return:                                           ; preds = %entry
  ret void
}

define void @static_initialization(int)(i32 %new_value) nounwind {
entry:
  %new_value_addr = alloca i32                    ; <i32*> [#uses=2]
  %0 = alloca i8                                  ; <i8*> [#uses=2]
  %retval.1 = alloca i8                           ; <i8*> [#uses=2]
  %"alloca point" = bitcast i32 0 to i32          ; <i32> [#uses=0]
  store i32 %new_value, i32* %new_value_addr
  %1 = load i8* bitcast (i64* @guard variable for static_initialization(int)::value to i8*), align 1 ; <i8> [#uses=1]
  %2 = icmp eq i8 %1, 0                           ; <i1> [#uses=1]
  br i1 %2, label %bb, label %bb2

bb:                                               ; preds = %entry
  %3 = call i32 @__cxa_guard_acquire(i64* @guard variable for static_initialization(int)::value) nounwind ; <i32> [#uses=1]
  %4 = icmp ne i32 %3, 0                          ; <i1> [#uses=1]
  %5 = zext i1 %4 to i8                           ; <i8> [#uses=1]
  store i8 %5, i8* %retval.1, align 1
  %6 = load i8* %retval.1, align 1                ; <i8> [#uses=1]
  %toBool = icmp ne i8 %6, 0                      ; <i1> [#uses=1]
  br i1 %toBool, label %bb1, label %bb2

bb1:                                              ; preds = %bb
  store i8 0, i8* %0, align 1
  %7 = load i32* %new_value_addr, align 4         ; <i32> [#uses=1]
  store i32 %7, i32* @static_initialization(int)::value, align 4
  store i8 1, i8* %0, align 1
  call void @__cxa_guard_release(i64* @guard variable for static_initialization(int)::value) nounwind
  br label %bb2

bb2:                                              ; preds = %bb1, %bb, %entry
  br label %return

return:                                           ; preds = %bb2
  ret void
}

declare i32 @__cxa_guard_acquire(i64*) nounwind

declare void @__cxa_guard_release(i64*) nounwind

define i32 @main() nounwind {
entry:
  %retval = alloca i32                            ; <i32*> [#uses=2]
  %0 = alloca i32                                 ; <i32*> [#uses=2]
  %"alloca point" = bitcast i32 0 to i32          ; <i32> [#uses=0]
  call void @no_static_initialization()() nounwind
  call void @static_initialization(int)(i32 1) nounwind
  %1 = call i32 @rand() nounwind                  ; <i32> [#uses=1]
  call void @static_initialization(int)(i32 %1) nounwind
  store i32 0, i32* %0, align 4
  %2 = load i32* %0, align 4                      ; <i32> [#uses=1]
  store i32 %2, i32* %retval, align 4
  br label %return

return:                                           ; preds = %entry
  %retval1 = load i32* %retval                    ; <i32> [#uses=1]
  ret i32 %retval1
}

declare i32 @rand() nounwind