G ++ 4.6 -std = gnu ++ 0x: static local variable Call time and thread safety constructs

void a() { ... } void b() { ... } struct X { X() { b(); } }; void f() { a(); static X x; ... } 

Suppose f is called several times from various threads (potentially connected) after entering main. (and, of course, the only calls a and b are those that were discussed above)

When the above code is compiled with gcc g ++ 4.6 in -std = gnu ++ 0x mode:

Q1. Is it guaranteed that a () will be called at least once and return before b () is called? That is, to ask, when you first call f (), is the constructor x, called at the same time a local local variable (not static) with automatic duration (for example, not in global static initialization time)?

Q2. Is it guaranteed that b () will be called exactly once? Even if two threads execute f for the first time at the same time on different cores? If so, through which specific mechanism generates GCC code that provides synchronization? Edit : Also, can one of the threads calling f () access x before the constructor of X is returned?

Update: I am trying to compile an example and decompile an investigation mechanism ...

test.cpp:

 struct X; void ext1(int x); void ext2(X& x); void a() { ext1(1); } void b() { ext1(2); } struct X { X() { b(); } }; void f() { a(); static X x; ext2(x); } 

Then:

 $ g++ -std=gnu++0x -c -o test.o ./test.cpp $ objdump -d test.o -M intel > test.dump 

test.dump:

 test.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <_Z1av>: 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: bf 01 00 00 00 mov edi,0x1 9: e8 00 00 00 00 call e <_Z1av+0xe> e: 5d pop rbp f: c3 ret 0000000000000010 <_Z1bv>: 10: 55 push rbp 11: 48 89 e5 mov rbp,rsp 14: bf 02 00 00 00 mov edi,0x2 19: e8 00 00 00 00 call 1e <_Z1bv+0xe> 1e: 5d pop rbp 1f: c3 ret 0000000000000020 <_Z1fv>: 20: 55 push rbp 21: 48 89 e5 mov rbp,rsp 24: 41 54 push r12 26: 53 push rbx 27: e8 00 00 00 00 call 2c <_Z1fv+0xc> 2c: b8 00 00 00 00 mov eax,0x0 31: 0f b6 00 movzx eax,BYTE PTR [rax] 34: 84 c0 test al,al 36: 75 2d jne 65 <_Z1fv+0x45> 38: bf 00 00 00 00 mov edi,0x0 3d: e8 00 00 00 00 call 42 <_Z1fv+0x22> 42: 85 c0 test eax,eax 44: 0f 95 c0 setne al 47: 84 c0 test al,al 49: 74 1a je 65 <_Z1fv+0x45> 4b: 41 bc 00 00 00 00 mov r12d,0x0 51: bf 00 00 00 00 mov edi,0x0 56: e8 00 00 00 00 call 5b <_Z1fv+0x3b> 5b: bf 00 00 00 00 mov edi,0x0 60: e8 00 00 00 00 call 65 <_Z1fv+0x45> 65: bf 00 00 00 00 mov edi,0x0 6a: e8 00 00 00 00 call 6f <_Z1fv+0x4f> 6f: 5b pop rbx 70: 41 5c pop r12 72: 5d pop rbp 73: c3 ret 74: 48 89 c3 mov rbx,rax 77: 45 84 e4 test r12b,r12b 7a: 75 0a jne 86 <_Z1fv+0x66> 7c: bf 00 00 00 00 mov edi,0x0 81: e8 00 00 00 00 call 86 <_Z1fv+0x66> 86: 48 89 d8 mov rax,rbx 89: 48 89 c7 mov rdi,rax 8c: e8 00 00 00 00 call 91 <_Z1fv+0x71> Disassembly of section .text._ZN1XC2Ev: 0000000000000000 <_ZN1XC1Ev>: 0: 55 push rbp 1: 48 89 e5 mov rbp,rsp 4: 48 83 ec 10 sub rsp,0x10 8: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi c: e8 00 00 00 00 call 11 <_ZN1XC1Ev+0x11> 11: c9 leave 12: c3 ret 

I do not see the synchronization mechanism? Or is it added to linktime?

Update2: Well, when I bind it, I see it ...

 400973: 84 c0 test %al,%al 400975: 75 2d jne 4009a4 <_Z1fv+0x45> 400977: bf 98 20 40 00 mov $0x402098,%edi 40097c: e8 1f fe ff ff callq 4007a0 < __cxa_guard_acquire@plt > 400981: 85 c0 test %eax,%eax 400983: 0f 95 c0 setne %al 400986: 84 c0 test %al,%al 400988: 74 1a je 4009a4 <_Z1fv+0x45> 40098a: 41 bc 00 00 00 00 mov $0x0,%r12d 400990: bf a0 20 40 00 mov $0x4020a0,%edi 400995: e8 a6 00 00 00 callq 400a40 <_ZN1XC1Ev> 40099a: bf 98 20 40 00 mov $0x402098,%edi 40099f: e8 0c fe ff ff callq 4007b0 < __cxa_guard_release@plt > 4009a4: bf a0 20 40 00 mov $0x4020a0,%edi 4009a9: e8 72 ff ff ff callq 400920 <_Z4ext2R1X> 4009ae: 5b pop %rbx 4009af: 41 5c pop %r12 4009b1: 5d pop %rbp 

He surrounds him with __cxa_guard_acquire and __ cxa_guard_release , whatever they do.

+6
source share
2 answers

Q1. Yes. According to C ++ 11, 6.7 / 4:

such a variable is initialized when the first control passes through its declaration

therefore, it will be initialized after the first call of a() .

Q2. In GCC and any compiler supporting the C ++ 11 stream model: yes, initializing local static variables is thread safe. Other compilers may not give this guarantee. The exact mechanism is an implementation detail. I believe that GCC uses an atomic flag to indicate whether it is initialized, and a mutex to protect initialization when the flag is not set, but I could be wrong. Of course, this thread implies that it was originally implemented like this.

UPDATE: your code does indeed contain an initialization code. You can see this more clearly if you linked it, and then parse the program so that you can see which functions are calling. I also used objdump -SC to alternate C ++ source and demang names. It uses the __cxa_guard_acquire and __cxa_guard_release internal blocking functions to ensure that only one thread executes the initialization code.

  #void f() #{ 400724: push rbp 400725: mov rbp,rsp 400728: push r13 40072a: push r12 40072c: push rbx 40072d: sub rsp,0x8 # a(); 400731: call 400704 <a()> # static X x; # if (!guard) { 400736: mov eax,0x601050 40073b: movzx eax,BYTE PTR [rax] 40073e: test al,al 400740: jne 400792 <f()+0x6e> # if (__cxa_guard_acquire(&guard)) { 400742: mov edi,0x601050 400747: call 4005c0 < __cxa_guard_acquire@plt > 40074c: test eax,eax 40074e: setne al 400751: test al,al 400753: je 400792 <f()+0x6e> # // initialise x 400755: mov ebx,0x0 40075a: mov edi,0x601058 40075f: call 4007b2 <X::X()> # __cxa_guard_release(&guard); 400764: mov edi,0x601050 400769: call 4005e0 < __cxa_guard_release@plt > # } else { 40076e: jmp 400792 <f()+0x6e> # // already initialised 400770: mov r12d,edx 400773: mov r13,rax 400776: test bl,bl 400778: jne 400784 <f()+0x60> 40077a: mov edi,0x601050 40077f: call 4005f0 < __cxa_guard_abort@plt > 400784: mov rax,r13 400787: movsxd rdx,r12d 40078a: mov rdi,rax 40078d: 400610 < _Unwind_Resume@plt > # } # } # ext2(x); 400792: mov edi,0x601058 400797: call 4007d1 <_Z4ext2R1X> #} 
+5
source

As far as I know, it is guaranteed that b is called only once. However, it is not guaranteed that the initialization is thread safe, which means that another thread can potentially work with half / not initialized x. (This is ridiculous because static mutexes are basically useless in this way.)

-1
source

Source: https://habr.com/ru/post/909818/


All Articles