局部靜態變量是如何做到只初始化一次的?


關於編譯選項 -Wa,-adlhn參考

http://blog.csdn.net/lanxinju/article/details/5900986

 

 

以下內容來自於內網別的高人的回復

可以寫個程序測試一下: 

class A
{
public:
    A() {}
public:
    int a;
};

int static_var_func()
{
    static A a;
    return a.a++;
}

int main(int argc, char * argv[])
{
    static_var_func();
    return 0;
} 

看看匯編

g++ -c -g -Wa,-adlhn yy.cpp

結果: 

  10:yy.cpp        **** int static_var_func()
  35                            .loc 1 10 0
  36 0000 55                    pushq   %rbp
  37                    .LCFI2:
  38 0001 4889E5                movq    %rsp, %rbp
  39                    .LCFI3:
  40                    .LBB2:
  11:yy.cpp        **** {
  12:yy.cpp        ****     static A a;
  41                            .loc 1 12 0
  42 0004 B8000000              movl    $_ZGVZ15static_var_funcvE1a, %eax
  42      00
  43 0009 0FB600                movzbl  (%rax), %eax
  44 000c 84C0                  testb   %al, %al
  45 000e 7527                  jne     .L4
  46 0010 BF000000              movl    $_ZGVZ15static_var_funcvE1a, %edi
  46      00
  47 0015 E8000000              call    __cxa_guard_acquire
  47      00
  48 001a 85C0                  testl   %eax, %eax
  49 001c 0F95C0                setne   %al
  50 001f 84C0                  testb   %al, %al
  51 0021 7414                  je      .L4
  52 0023 BF000000              movl    $_ZZ15static_var_funcvE1a, %edi
  52      00
  53 0028 E8000000              call    _ZN1AC1Ev
  53      00
  54 002d BF000000              movl    $_ZGVZ15static_var_funcvE1a, %edi
  54      00
  55 0032 E8000000              call    __cxa_guard_release
  55      00
  56                    .L4:
  13:yy.cpp        ****     return a.a++;
  57                            .loc 1 13 0
  58 0037 8B050000              movl    _ZZ15static_var_funcvE1a(%rip), %eax
  58      0000
  59 003d 89C2                  movl    %eax, %edx
  60 003f 83C001                addl    $1, %eax
  61 0042 89050000              movl    %eax, _ZZ15static_var_funcvE1a(%rip)
  61      0000
  62 0048 89D0                  movl    %edx, %eax
  63                    .LBE2:
  14:yy.cpp        **** }
  64                            .loc 1 14 0
  65 004a C9                    leave
  66 004b C3                    ret

 亮點就在__cxa_guard_acquire和__cxa_guard_release上,這兩個函數實現於libstdc++。大意是一個全局的mutex和一個cond來保護一個鎖變量(_ZGVZ15static_var_funcvE1a),鎖變量再來保護目標變量(_ZZ15static_var_funcvE1a)。鎖變量的第一個字節(也就是%al)表示目標變量是否被初始化過了,第二個字節表示目標變量是否在初始化中。__cxa_guard_acquire的時候將鎖變量的第二個字節置1,表示初始化中;如果已經為1了,就等待它變0(通過全局cond)再返回(保證初始化結束)。__cxa_guard_release的時候清除第二個字節,再將第一個字節置1。

 簡單地說,g++在變量初始化的前后,自動加了鎖保護代碼。

 另外還有一種可能的情況,變量初始化的時候,如果存在自引用,可能會循環初始化產生死鎖。g++對這種情況也有考慮。

 

http://www.dutor.net/index.php/2013/06/initialization-of-local-static-variables/

  不必說靜態變量和普通變量的區別,也不必說靜態變量及其作用域的得與失,單單說一下函數作用域的靜態變量是如何初始化的。

1
2
3
4
5
6
int foo()
{
    static int n = init();
    //~ do anything/nothing on 'n'
    return n;
}

  在 foo() 第一次被調用時,foo()::s 只初始化一次(C 中,靜態變量只允許以常量初始化)。“只初始化一次”是如何保證的呢?當然需要編譯器維護一個狀態,來標識該變量是否已被初始化,並安插代碼,在每一次函數被調用時進行判斷。咱們通過匯編驗證一把:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
.globl _Z3foov
  .type _Z3foov, @function
_Z3foov:
.LFB1405:
  pushq %rbp
.LCFI13:
  movq  %rsp, %rbp
.LCFI14:
  movl  $_ZGVZ3foovE1n, %eax
  movzbl  (%rax), %eax
  testb %al, %al 
  jne .L18
  movl  $_ZGVZ3foovE1n, %edi
  call  __cxa_guard_acquire
  testl %eax, %eax
  setne %al 
  testb %al, %al 
  je  .L18
  call  _Z4initv
  movl  %eax, _ZZ3foovE1n(%rip)
  movl  $_ZGVZ3foovE1n, %edi
  call  __cxa_guard_release
.L18:
  movl  _ZZ3foovE1n(%rip), %eax
  leave
  ret

  寄存器、指令和標號不提,其他符號是什么含義呢?通過 c++filt 進行 demangling,_ZGVZ3foovE1n 標識 ‘guard variable for foo()::n’,作為前面提到的“初始化狀態標識”用(低字節),_ZZ3foovE1n 標識 ‘foo()::n’,_Z4initv 即 init()。
  那 __cxa_guard_acquire 和 __cxa_guard_release 呢?故名思議,這兩個函數具有鎖語義。為什么需要鎖呢?當然是基於靜態變量的線程安全考慮了。靜態變量的狀態變化屬於業務邏輯,編譯器管不着也管不了,但靜態變量的初始化過程由編譯器負責,在初始化線程安全的問題上還是可以出把力的。
  分析上述匯編代碼。首先獲取 guard 變量,判斷低字節是否為 0,若非零,表示已經初始化,可以直接使用。否則,將 guard 作為參數調用 __cxa_guard_acquire,如果鎖成功,調用 init() 初始化靜態變量 foo()::n,然后釋放鎖。如果鎖失敗,說明產生競態條件,則會阻塞當前線程,不同於普通鎖的地方在於,__cxa_guard_acquire 是有返回值的(當然 pthread_lock 也有返回值,但用途不同),如果發生了等待,__cxa_guard_acquire 返回 0,並不會進入 foo()::n 的初始化過程(其他線程已經初始化過了,初始化失敗的情況就不細究了)。
  為了驗證上述分析,可以將 init() 實現成一個耗時的操作,令多個線程“同時”調用 foo(),然后查看各個線程的運行狀態。
  利用該機制,可以很好的實現所謂 Singleton 模式:

1
2
3
4
5
Singleton* Singleton::GetInstance()
{
    static Singleton instance;
    return &instance;
}

  對於單線程程序,靜態變量的保護是沒有必要的,g++ 的 -fno-threadsafe-statics 選項可以禁掉該機制。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM