局部静态变量是如何做到只初始化一次的?

关于编译选项 -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 选项可以禁掉该机制。

posted on 2015-07-16 23:35  阿笨猫  阅读(10145)  评论(0编辑  收藏  举报