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

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

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

Предположим, что f вызывается несколько раз из различных потоков (потенциально связанных) после ввода main. (И, конечно, что только звонки на А и В, которые наблюдаются выше)G ++ 4.6 -std = gnu ++ 0x: статическая локальная переменная Конструкция времени вызова и защита потока

Если приведенный выше код компилируется с GCC G ++ 4.6 в -std = ГНУ ++ 0x режиме:

Q1 , Гарантировано ли, что a() будет вызываться хотя бы один раз и вернуться до вызова b()? То есть спросить, при первом вызове f(), является конструктором x, называемым в то же время локальной локальной переменной (нестатической) продолжительности времени (например, не в глобальном статическом времени инициализации)?

Q2. Гарантировано ли, что b() будет вызываться ровно один раз? Даже если два потока выполняются f впервые в одно и то же время на разных ядрах? Если да, то с помощью которого конкретный механизм генерирует код GCC для синхронизации? Редактировать: Кроме того, один из потоков, вызывающих f(), получает доступ к x до того, как возвращается конструктор X?

Обновление: Я пытаюсь скомпилировать пример и декомпилировать исследовать механизм ...

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

Тогда:

$ 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  

Я не вижу механизм синхронизации? Или он добавлен в linktime?

Update2: Хорошо, когда я связываю это я могу видеть его ...

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 

Она окружает его с __cxa_guard_acquire и __cxa_guard_release, что они делают.

+0

Q2: гарантируется ровно один раз. C++ 11 знает о потоках и указан в стандарте. Что касается меканизма, не знаю, но он не может сильно отличаться от мьютекса;). –

+0

Q2: гарантировано (как уже указывали другие). Q1: Я бы не стал делать ставку на это. Объявление статической переменной вряд ли является частью потока программы, а оптимизаторы - это печально известные коды. Во всяком случае, мне бы очень хотелось поддерживать код, который зависит от таких тонкостей. – stefaanv

+0

@stefaanv: Неверно. Заказ гарантируется как стандартом, так и реализацией. –

ответ

5

Q1. Да. В соответствии с С ++ 11, 6,7/4:

такая переменная инициализируется первый контроль времени проходит через его декларации

поэтому он будет инициализирован после первого вызова a().

Q2. В GCC и любом компиляторе, поддерживающем модель потока C++ 11: да, инициализация локальных статических переменных является потокобезопасной. Другие компиляторы могут не дать эту гарантию. Точный механизм - это деталь реализации.Я считаю, что GCC использует атомный флаг, чтобы указать, инициализирован ли он, и мьютекс для защиты инициализации, когда флаг не установлен, но я мог ошибаться. Конечно, this thread подразумевает, что он был первоначально реализован именно так.

UPDATE: ваш код действительно содержит код инициализации. Вы можете увидеть это более четко, если вы его связали, а затем разобрать программу, чтобы вы могли видеть, какие функции вызывают. Я также использовал objdump -SC для чередования исходных и деманговых имен C++. Он использует функции внутренней блокировки __cxa_guard_acquire и __cxa_guard_release, чтобы убедиться, что только один поток выполняет инициализационный код.

#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

См. Мое обновление ... Как я могу увидеть механизм синхронизации? –

-1

Насколько я знаю, гарантируется, что b вызывается только один раз. Тем не менее, не гарантируется, что инициализация выполняется поточно-безопасным, что означает, что другой поток потенциально может работать с половиной/не инициализированной x. (Это смешно, потому что статические мьютексы в основном бесполезны таким образом.)

+0

Я думаю, что это не поведение в C++ 11 с данным параметром командной строки. Однако это было правильно в более старом стандарте. –

+0

C++ 11 требует инициализации с потоком, и GCC предоставил его по умолчанию в течение довольно долгого времени. –

+0

Это сообщение может быть улучшено, если вы не ошибаетесь. –