그것은 실제로 재진입 은 아니다 . 동일한 스레드 (또는 다른 스레드)에서 함수를 두 번 실행하지 않습니다 . 재귀를 통해 또는 현재 함수의 주소를 콜백 함수 포인터 인수로 다른 함수에 전달하여 얻을 수 있습니다. (동기화되기 때문에 안전하지 않습니다).
이 신호 처리기 및 메인 스레드 사이에 그냥 일반 바닐라 데이터 레이스 UB (정의되지 않은 동작)입니다 : 만 sig_atomic_t
이 안전 보장된다 . 8 바이트 객체를 x86-64에서 하나의 명령으로로드하거나 저장할 수 있고 컴파일러가 해당 asm을 선택하는 경우와 같이 다른 것들이 작동 할 수 있습니다. (@icarus의 답변이 보여 주듯이).
단일 코어 마이크로 컨트롤러의 인터럽트 핸들러는 기본적으로 단일 스레드 프로그램의 신호 핸들러와 동일합니다. MCU 프로그래밍-C ++ O2 최적화가 중단되는 동안 루프를 참조하십시오 . 이 경우 UB의 결과는로드가 루프에서 게양되었습니다.
데이터 레이싱 UB로 인해 실제로 발생하는 테어 링 테스트 사례는 아마도 32 비트 모드에서 또는 struct 멤버를 별도로로드하는 오래된 멍청한 컴파일러로 개발 / 테스트되었을 것입니다.
귀하의 경우, 컴파일러는 UB 프리 프로그램이 관찰 할 수 없기 때문에 무한 루프에서 저장소를 최적화 할 수 있습니다. data
없다 _Atomic
거나volatile
, 루프의 다른 부작용이 없다. 따라서 어떤 독자도이 작가와 동기화 할 수있는 방법이 없습니다. 이것은 최적화가 활성화 된 상태에서 컴파일하면 발생합니다 ( Godbolt 는 메인의 하단에 빈 루프를 보여줍니다). 또한 구조체를 two로 변경했으며 long long
gcc는 movdqa
루프 전에 단일 16 바이트 저장소를 사용합니다. (이것은 원 자성 이 보장 되지는 않지만 , 실제로는 정렬 된 것으로 가정하거나 거의 모든 CPU에서 또는 인텔에서 캐시 라인 경계를 넘지 않는다고 가정합니다. x86에서 자연스럽게 정렬 된 변수 원자에 정수 할당이 왜 필요한가요? )
따라서 최적화를 사용하여 컴파일하면 테스트가 중단되고 매번 동일한 값이 표시됩니다. C는 이식 가능한 어셈블리 언어가 아닙니다.
volatile struct two_int
또한 컴파일러가 최적화하지 못하게하지만 전체 구조체를 원자 적으로로드 / 저장하도록 강제 하지는 않습니다 . (그것은 않을 것 중지 참고.하지만, 하나 그렇게에서)를 volatile
않습니다 하지 데이터 레이스 UB를 방지,하지만 실제로는 스레드 간 통신을위한 충분한의 사람들이 (인라인 ASM과 함께) 손으로 압연 아토을 구축하는 방법이었다 일반 CPU 아키텍처의 경우 C11 / C ++ 11 이전 그들은있는 거 캐시 일관성 이렇게 volatile
이다 에 대부분 비슷한 연습 _Atomic
과memory_order_relaxed
순수한 부하 및 순수 상점, 유형에 사용되는 경우에 당신이 찢어하지 않도록 컴파일러는 단일 명령어를 사용합니다 충분히 좁힐. 그리고 물론volatile
_Atomic
및 mo_relaxed를 사용하여 동일한 asm으로 컴파일하는 코드 작성과 ISO C 표준의 보증은 없습니다 .
당신이 한 기능이 있다면 global_var++;
온 int
또는 long long
당신이 주에서 실행하는 것이 및 신호 처리기에서 비동기 적으로, 즉 데이터-레이스 UB를 만드는 사용 재진입 할 수있는 방법이 될 것입니다.
컴파일 된 방법에 따라 (메모리 대상 inc 또는 add 또는 load / inc / store를 분리하기 위해) 동일한 스레드의 신호 처리기와 관련하여 원자 적이거나 그렇지 않습니다. 'int num'에 대해 num ++가 원자 성일 수 있습니까?를 참조하십시오 . x86 및 C ++의 원자성에 대해 자세히 알아보십시오. (C11 stdatomic.h
과 _Atomic
속성은 C ++ 11의 std::atomic<T>
템플릿 과 동등한 기능을 제공합니다 )
명령 도중에 인터럽트 나 다른 예외가 발생할 수 없으므로 메모리 대상 추가는 원자 wrt입니다. 단일 코어 CPU의 컨텍스트 스위치. (캐시 코 히어 런트) DMA 기록기 만이 단일 코어 CPU add [mem], 1
의 lock
프리픽스 없이 증분 "스텝"할 수 있습니다 . 다른 스레드가 실행될 수있는 다른 코어가 없습니다.
따라서 신호의 경우와 유사합니다. 신호 처리기는 스레드를 정상적으로 처리하여 신호를 처리하는 대신 실행되므로 한 명령의 중간에서 처리 할 수 없습니다.