2012-03-02 5 views
6
void a() { ... } 
void b() { ... } 

struct X 
{ 
    X() { b(); } 
}; 

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

Supongamos que se llama varias veces desde varios subprocesos (potencialmente disputados) después de la entrada de main. (Y por supuesto que las únicas llamadas a a y b son los que se ve arriba)G ++ 4.6 -std = gnu ++ 0x: Constructor de variables locales estáticas Segmentación de llamadas y seguridad de subprocesos

Cuando el código anterior se compila con gcc g ++ 4,6 en -std = gnu ++ 0x modo:

Q1 . ¿Se garantiza que se llamará a() al menos una vez y se devolverá antes de llamar a b()? Es decir, en la primera llamada a f(), ¿es el constructor de x llamado al mismo tiempo una variable local de duración automática (no estática) (y no en el tiempo de inicialización estática global, por ejemplo)?

Q2. ¿Se garantiza que b() se llamará exactamente una vez? Incluso si dos hilos ejecutan f por primera vez al mismo tiempo en diferentes núcleos? En caso afirmativo, ¿mediante qué mecanismo específico el código generado por GCC proporciona sincronización? Editar: Además, ¿podría uno de los subprocesos llamar a f() obtener acceso a x antes de que el constructor de X regrese?

Actualización: Estoy tratando de compilar y descompilar un ejemplo para investigar el mecanismo ...

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); 
} 

continuación:

$ 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  

¿No veo el mecanismo de sincronización? ¿O se agrega en el tiempo de enlace?

Update2: Ok cuando me ligo lo puedo ver ...

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 <[email protected]> 
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 <[email protected]> 
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 

Rodea con __cxa_guard_acquire y __cxa_guard_release, hagan lo que hagan.

+0

Q2: garantizado exactamente una vez. C++ 11 conoce los hilos y eso fue especificado en el estándar. En cuanto al mecanismo, ninguna idea, pero no puede ser muy diferente de un mutex;). –

+0

Q2: garantizado (como otros ya se indica). P1: No apostaría por eso. Declarar una variable estática casi no forma parte del flujo del programa y los optimizadores son notorios descodificadores de código. De todos modos, odiaría mantener un código que dependa de sutilezas como esta. – stefaanv

+0

@stefaanv: Incorrecto. El orden está garantizado tanto por el estándar como por la implementación. –

Respuesta

5

Q1. Sí. Según C++ 11, 6,7/4:

tal variable se inicializa el primer control de tiempo pasa a través de su declaración

por lo que se inicializa después de la primera llamada a a().

Q2. Bajo GCC, y cualquier compilador que soporte el modelo de subprocesamiento de C++ 11: sí, la inicialización de variables estáticas locales es segura para subprocesos. Otros compiladores pueden no dar esa garantía. El mecanismo exacto es un detalle de implementación.Creo que GCC usa una bandera atómica para indicar si se inicializó, y un mutex para proteger la inicialización cuando la bandera no está configurada, pero podría estar equivocado. Ciertamente, this thread implica que originalmente se implementó así.

ACTUALIZACIÓN: su código efectivamente contiene el código de inicialización. Puede verlo más claramente si lo vincula y luego desarma el programa para que pueda ver qué funciones se están llamando. También usé objdump -SC para intercalar la fuente y exigir los nombres de C++. Utiliza las funciones de bloqueo interno __cxa_guard_acquire y __cxa_guard_release, para asegurarse de que solo un hilo ejecuta el código de inicialización.

#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 <[email protected]> 
    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 <[email protected]> 

    #  } 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 <[email protected]> 
    400784: mov rax,r13 
    400787: movsxd rdx,r12d 
    40078a: mov rdi,rax 
    40078d: 400610 <[email protected]> 

    #  } 
    # } 
    # ext2(x); 
    400792: mov edi,0x601058 
    400797: call 4007d1 <_Z4ext2R1X> 
    #} 
+0

Ver mi actualización ... ¿Cómo puedo ver el mecanismo de sincronización? –

-1

Por lo que sé, se garantiza que b solo se llame una vez. Sin embargo, no se garantiza que la inicialización se realice con seguridad de subprocesos, lo que significa que otro subproceso podría funcionar potencialmente con una mitad/no inicializado x. (Eso es algo divertido porque los mutexes estáticos son básicamente inútiles de esta manera.)

+0

Creo que este no es el comportamiento en C++ 11 habilitado con el parámetro de línea de comando dado. Sin embargo, era correcto en el estándar anterior. –

+0

C++ 11 requiere una inicialización segura de subprocesos, y GCC lo ha proporcionado de forma predeterminada durante bastante tiempo. –

+0

Esta publicación podría mejorarse al no ser incorrecta. –

Cuestiones relacionadas