關於編譯選項 -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 選項可以禁掉該機制。