Как MSVC оптимизирует использование статических переменных? - PullRequest
13 голосов
/ 01 октября 2010

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

Мой код:

#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 имеет отношение к этому, но я не совсем уверен)?

Ответы [ 2 ]

8 голосов
/ 01 октября 2010

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

Флаг, кажется, расположен по адресу 0x00403374 и занимает байт, а сама переменная расположена по адресу 0x00403370.

3 голосов
/ 01 октября 2010

Мне нравится использовать 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...