IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    g++ 函数域静态变量的初始化

    dutor发表于 2013-06-20 17:50:17
    love 0

      不必说静态变量和普通变量的区别,也不必说静态变量及其作用域的得与失,单单说一下函数作用域的静态变量是如何初始化的。

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

    over.



沪ICP备19023445号-2号
友情链接