用于C ++范围的静态初始化的汇编代码

时间:2014-02-27 16:12:43

标签: c++ multithreading gcc assembly static-variables

我从

中读到了一些关于本地范围的静态变量初始化顺序问题的旧文章

C++ scoped static initialization is not thread-safe早在2004年,

Function Static Variables in Multi-Threaded Environments 2006年。

然后我开始制作一个例子并检查我的编译器,gcc 4.4.7

int calcSomething(){}

void foo(){
    static int x = calcSomething();
}

int main(){
    foo();
    return 0;
}

来自objdump的结果显示:

000000000040061a <_Z3foov>:
  40061a:   55                      push   %rbp
  40061b:   48 89 e5                mov    %rsp,%rbp
  40061e:   b8 d0 0a 60 00          mov    $0x600ad0,%eax
  400623:   0f b6 00                movzbl (%rax),%eax
  400626:   84 c0                   test   %al,%al
  400628:   75 28                   jne    400652 <_Z3foov+0x38>
  40062a:   bf d0 0a 60 00          mov    $0x600ad0,%edi
  40062f:   e8 bc fe ff ff          callq  4004f0 <__cxa_guard_acquire@plt>
  400634:   85 c0                   test   %eax,%eax
  400636:   0f 95 c0                setne  %al
  400639:   84 c0                   test   %al,%al
  40063b:   74 15                   je     400652 <_Z3foov+0x38>
  40063d:   e8 d2 ff ff ff          callq  400614 <_Z13calcSomethingv>
  400642:   89 05 90 04 20 00       mov    %eax,0x200490(%rip)        # 600ad8 <_ZZ3foovE1x>
  400648:   bf d0 0a 60 00          mov    $0x600ad0,%edi
  40064d:   e8 be fe ff ff          callq  400510 <__cxa_guard_release@plt>
  400652:   c9                      leaveq 
  400653:   c3                      retq   

不幸的是,我对asssmbly代码的了解是如此有限,以至于我无法分辨编译器在这里做了什么。任何人都可以解释一下这个汇编代码的作用吗?它仍然不是线程安全的吗?我非常感谢一些“伪代码”,显示了gcc在这里做的事情。

EDIT-1: 正如Jerry评论的那样,我使用O2启用了优化,汇编代码为:

0000000000400620 <_Z3foov>:
  400620:   48 83 ec 08             sub    $0x8,%rsp
  400624:   80 3d 85 04 20 00 00    cmpb   $0x0,0x200485(%rip)        # 600ab0 <_ZGVZ3foovE1x>
  40062b:   74 0b                   je     400638 <_Z3foov+0x18>
  40062d:   48 83 c4 08             add    $0x8,%rsp
  400631:   c3                      retq   
  400632:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
  400638:   bf b0 0a 60 00          mov    $0x600ab0,%edi
  40063d:   e8 9e fe ff ff          callq  4004e0 <__cxa_guard_acquire@plt>
  400642:   85 c0                   test   %eax,%eax
  400644:   74 e7                   je     40062d <_Z3foov+0xd>
  400646:   c7 05 68 04 20 00 00    movl   $0x0,0x200468(%rip)        # 600ab8 <_ZZ3foovE1x>
  40064d:   00 00 00 
  400650:   bf b0 0a 60 00          mov    $0x600ab0,%edi
  400655:   48 83 c4 08             add    $0x8,%rsp
  400659:   e9 a2 fe ff ff          jmpq   400500 <__cxa_guard_release@plt>
  40065e:   66 90                   xchg   %ax,%ax

1 个答案:

答案 0 :(得分:4)

是。在伪代码中(对于未优化的情况),它类似于:

if (flag_val() != 0) goto done;
if (guard_acquire() != 0) goto done;
x = calcSomething();
guard_release_and_set_flag();
// Note releasing the guard lock causes later 
// calls to flag_val() to return non-zero.
done: return

flag_val()实际上是一种非阻塞检查,显然是为了避免在必要时调用acquire原语的效率。该标志必须由guard_release设置,如图所示。 acquire似乎是同步调用以获取锁定。只有一个线程将获得真值并执行初始化。在释放锁之后,非零标志会阻止锁的任何进一步触摸。

另一个有趣的消息是,保护数据结构与静态存储器中x本身的值相差8个字节。

熟悉具有内置线程的语言中的单例模式的人,例如Java会认识到这一点!

<强>加成

现在多一点时间,所以更详细一点:

000000000040061a <_Z3foov>:

  ; Prepare to access stack variables (never used in un-optimized code).
  40061a:   55                      push   %rbp
  40061b:   48 89 e5                mov    %rsp,%rbp

  ; Test a byte 8 away from the static int x. This is apparently an "initialized" flag.
  40061e:   b8 d0 0a 60 00          mov    $0x600ad0,%eax
  400623:   0f b6 00                movzbl (%rax),%eax
  400626:   84 c0                   test   %al,%al

  ; Goto the end of the function if the byte was no-zero.
  400628:   75 28                   jne    400652 <_Z3foov+0x38>

  ; Load the same byte address in di: the argument for the call to 
  ; acquire the guard lock. 
  40062a:   bf d0 0a 60 00          mov    $0x600ad0,%edi
  40062f:   e8 bc fe ff ff          callq  4004f0 <__cxa_guard_acquire@plt>

  ; Test the return value. Goto end of function if not zero (non-optimized code).
  400634:   85 c0                   test   %eax,%eax
  400636:   0f 95 c0                setne  %al
  400639:   84 c0                   test   %al,%al
  40063b:   74 15                   je     400652 <_Z3foov+0x38>

  ; Call the user's initialization function and move result into x.
  40063d:   e8 d2 ff ff ff          callq  400614 <_Z13calcSomethingv>
  400642:   89 05 90 04 20 00       mov    %eax,0x200490(%rip)        # 600ad8 <_ZZ3foovE1x>

  ; Load the guard byte's address again and call the release routine.
  ; This must set the flag to non-zero.
  400648:   bf d0 0a 60 00          mov    $0x600ad0,%edi
  40064d:   e8 be fe ff ff          callq  400510 <__cxa_guard_release@plt>

  ; Restore state and return.
  400652:   c9                      leaveq 
  400653:   c3                      retq   

This listing,虽然对于LLVM编译器而不是g ++(你运行OS X吗?OS X将g ++别名为LLVM),但同意上面的猜测。 set_initialized例程正在guard_release中设置标记值。