C ++ volatile 키워드가 메모리 울타리를 도입합니까? 도입해야합니까? 내 이해에서

volatile컴파일러에게 값이 변경 될 수 있음 을 알리는 것을 이해 하지만이 기능을 수행하려면 컴파일러가 작동하도록 메모리 펜스를 도입해야합니까?

내 이해에서 휘발성 개체에 대한 작업 순서는 재정렬 할 수 없으며 보존해야합니다. 이것은 일부 메모리 펜스가 필요하며 실제로이 문제를 해결할 방법이 없음을 의미하는 것 같습니다. 이 말이 맞습니까?


이 관련 질문에 흥미로운 토론 이 있습니다.

Jonathan Wakely는 다음과 같이 씁니다 .

… 별개의 휘발성 변수에 대한 액세스는 별도의 전체 표현식에서 발생하는 한 컴파일러에서 재정렬 할 수 없습니다. 휘발성은 스레드 안전성에 쓸모가 없지만 그가 제공하는 이유가 아닙니다. 컴파일러가 휘발성 객체에 대한 액세스를 재정렬 할 수 있기 때문이 아니라 CPU가이를 재정렬 할 수 있기 때문입니다. 원자 적 연산과 메모리 장벽으로 인해 컴파일러와 CPU의 순서가 변경되지 않습니다.

되는 데이비드 슈워츠는 응답 코멘트에 :

… C ++ 표준의 관점에서 보면 컴파일러가 무언가를 수행하는 것과 컴파일러가 하드웨어가 무언가를 수행하도록하는 명령어를 방출하는 것 사이에는 차이가 없습니다. CPU가 휘발성에 대한 액세스를 재정렬 할 수 있다면 표준은 순서를 유지할 것을 요구하지 않습니다. …

… C ++ 표준은 재정렬이 무엇인지 구분하지 않습니다. 그리고 CPU가 관찰 가능한 효과없이 순서를 변경할 수 있다고 주장 할 수는 없습니다. C ++ 표준은 순서를 관찰 가능한 것으로 정의합니다. 컴파일러는 플랫폼이 표준에서 요구하는 작업을 수행하도록하는 코드를 생성하는 경우 플랫폼에서 C ++ 표준을 준수합니다. 표준이 휘발성 물질에 대한 액세스를 재정렬하지 않도록 요구하는 경우 재정렬하는 플랫폼은 규정을 준수하지 않습니다. …

내 요점은 C ++ 표준이 컴파일러가 개별 휘발성에 대한 액세스 순서를 재정렬하는 것을 금지하는 경우 이러한 액세스 순서가 프로그램의 관찰 가능한 동작의 일부라는 이론에 따라 컴파일러가 CPU가 수행하는 것을 금지하는 코드를 내보내도록 요구한다는 것입니다. 그래서. 표준은 컴파일러가하는 일과 컴파일러의 생성 코드가 CPU가하는 일을 구별하지 않습니다.

다음 중 두 가지 질문이 생성됩니다. 둘 중 하나가 “맞습니까?” 실제 구현은 실제로 무엇을합니까?



답변

무엇을하는지 설명하는 대신 volatile을 사용해야하는시기를 설명하겠습니다 volatile.

  • 시그널 핸들러 안에있을 때. volatile변수에 쓰는 것은 표준이 신호 처리기 내에서 할 수있는 유일한 작업이기 때문입니다. C ++ 11부터는 std::atomic그 목적으로 사용할 수 있지만 원자가 잠금이없는 경우에만 사용할 수 있습니다 .
  • setjmp 인텔에 따라 다룰 때 .
  • 하드웨어를 직접 처리 할 때 컴파일러가 읽기 또는 쓰기를 최적화하지 않도록해야합니다.

예를 들면 :

volatile int *foo = some_memory_mapped_device;
while (*foo)
    ; // wait until *foo turns false

volatile지정자가 없으면 컴파일러는 루프를 완전히 최적화 할 수 있습니다. volatile지정은 이후 2가 같은 값을 반환 읽는 가정하지 않을 수 컴파일러를 알려줍니다.

volatile스레드와는 아무 상관이있다. 위의 예제는 *foo관련된 취득 작업이 없기 때문에 쓰는 다른 스레드가있는 경우 작동하지 않습니다 .

다른 모든 경우에는 volatileC ++ 11 이전 컴파일러 및 컴파일러 확장 (예 /volatile:ms: X86 / I64에서 기본적으로 활성화되는 msvc의 스위치)을 처리 할 때를 제외하고는을 (를) 이식 할 수없는 것으로 간주하고 더 이상 코드 검토를 통과하지 않아야 합니다.


답변

C ++ volatile 키워드가 메모리 울타리를 도입합니까?

사양을 준수하는 C ++ 컴파일러는 메모리 펜스를 도입 할 필요가 없습니다. 특정 컴파일러는 할 수 있습니다. 컴파일러 작성자에게 질문을 보내십시오.

C ++에서 “휘발성”기능은 스레딩과 관련이 없습니다. “휘발성”의 목적은 외부 조건으로 인해 변경되는 레지스터에서 읽는 것이 최적화되지 않도록 컴파일러 최적화를 비활성화하는 것입니다. 다른 CPU의 다른 스레드에 의해 기록되는 메모리 주소가 외부 조건으로 인해 변경되는 레지스터입니까? 다시 말하지만, 일부 컴파일러 작성자가 외부 조건으로 인해 레지스터가 변경되는 것처럼 서로 다른 CPU의 서로 다른 스레드에 의해 기록되는 메모리 주소를 처리하도록 선택 했다면 그것이 바로 그들의 업무입니다. 그렇게 할 필요는 없습니다. 예를 들어 메모리 울타리를 도입하더라도 모든 스레드가 일관된 휘발성 읽기 및 쓰기 순서.

사실, volatile은 C / C ++의 스레딩에 거의 쓸모가 없습니다. 가장 좋은 방법은 그것을 피하는 것입니다.

또한 메모리 펜스는 특정 프로세서 아키텍처의 구현 세부 사항입니다. volatile 멀티 스레딩을 위해 명시 적 으로 설계된 C #에서는 프로그램이 처음에 펜스가없는 아키텍처에서 실행될 수 있기 때문에 사양에서 하프 펜스가 도입 될 것이라고 말하지 않습니다. 오히려 사양은 일부 부작용이 정렬되는 방식에 대한 특정 (매우 약한) 제약 조건을 설정하기 위해 컴파일러, 런타임 및 CPU가 어떤 최적화를 피할 것인지에 대한 확실한 (매우 약함) 보장을합니다. 실제로 이러한 최적화는 하프 펜스를 사용하여 제거되지만 이는 향후 변경 될 수있는 구현 세부 사항입니다.

멀티 스레딩과 관련된 모든 언어의 휘발성 의미에 관심이 있다는 사실은 스레드간에 메모리를 공유하는 것에 대해 생각하고 있음을 나타냅니다. 그렇게하지 않는 것을 고려하십시오. 이는 프로그램을 이해하기 훨씬 어렵게 만들고 미묘하고 재현 불가능한 버그를 포함 할 가능성이 훨씬 더 높습니다.


답변

David가 간과하는 것은 C ++ 표준이 특정 상황에서만 상호 작용하는 여러 스레드의 동작을 지정하고 다른 모든 것은 정의되지 않은 동작을 초래한다는 사실입니다. 원자 변수를 사용하지 않으면 하나 이상의 쓰기와 관련된 경쟁 조건이 정의되지 않습니다.

따라서 CPU는 동기화 누락으로 인해 정의되지 않은 동작을 나타내는 프로그램의 차이 만 인식하므로 컴파일러는 동기화 명령을 무시할 권리가 있습니다.


답변

우선, C ++ 표준은 원 자성이 아닌 읽기 / 쓰기를 올바르게 정렬하는 데 필요한 메모리 장벽을 보장하지 않습니다. 휘발성 변수는 MMIO, 신호 처리 등과 함께 사용하는 데 권장됩니다. 대부분의 구현에서 휘발성 은 멀티 스레딩에 유용하지 않으며 일반적으로 권장되지 않습니다.

휘발성 액세스의 구현과 관련하여 이것이 컴파일러 선택입니다.

gcc 동작을 설명하는 이 기사에서는 휘발성 메모리에 대한 쓰기 순서를 지정하기 위해 휘발성 객체를 메모리 장벽으로 사용할 수 없음을 보여줍니다.

icc 동작 과 관련 하여이 소스 는 휘발성이 메모리 액세스 순서를 보장하지 않는다고 말하고 있습니다.

Microsoft VS2013 컴파일러에는 다른 동작이 있습니다. 이 문서 는 volatile 이 Release / Acquire 의미론을 적용하고 휘발성 객체가 다중 스레드 애플리케이션의 잠금 / 해제에 사용되도록하는 방법을 설명합니다.

고려해야 할 또 다른 측면은 동일한 컴파일러가 wrt 동작다를 수 있다는 것 입니다. 대상 하드웨어 아키텍처에 따라 휘발성으로 . MSVS 2013 컴파일러에 관한 이 게시물 은 ARM 플랫폼 용 volatile로 컴파일하는 세부 사항을 명확하게 설명합니다.

그래서 내 대답 :

C ++ volatile 키워드가 메모리 울타리를 도입합니까?

될 것이다 : 아마,하지 보장하지만, 어떤 컴파일러는 그것을 할 수 없습니다. 그 사실에 의존해서는 안됩니다.


답변

컴파일러는 내가 아는 한 Itanium 아키텍처에만 메모리 펜스를 삽입합니다.

volatile키워드는 정말 최고의 비동기 예를 들어, 변경, 신호 처리기 및 메모리 매핑 레지스터에 사용됩니다; 일반적으로 다중 스레드 프로그래밍에 사용하는 것은 잘못된 도구입니다.


답변

컴파일러 “컴파일러”에 따라 다릅니다. Visual C ++는 2005 년부터 지원합니다. 그러나 표준에서는 필요하지 않으므로 다른 컴파일러에서는 필요하지 않습니다.


답변

이것은 주로 메모리에서 발생하며 스레드없이 C ++ 11 이전 버전을 기반으로합니다. 그러나 커밋에서 스레딩에 대한 토론에 참여한 결과, volatile스레드 간의 동기화에 사용할 수있는 위원회의 의도는 없었습니다 . 마이크로 소프트가 제안했지만 그 제안은 이행되지 않았습니다.

의 주요 사양은 volatile휘발성에 대한 액세스가 IO와 마찬가지로 “관찰 가능한 동작”을 나타낸다는 것입니다. 같은 방식으로 컴파일러는 특정 IO를 재정렬하거나 제거 할 수 없으며, 휘발성 개체에 대한 액세스를 재정렬하거나 제거 할 수 없습니다 (또는 더 정확하게는 휘발성 한정 형식을 사용하여 lvalue 표현식을 통해 액세스). volatile의 원래 의도는 사실 메모리 매핑 IO를 지원하는 것이 었습니다. 그러나 이것의 “문제”는 “휘발성 액세스”를 구성하는 것이 정의 된 구현이라는 것입니다. 그리고 많은 컴파일러는 정의가 “메모리를 읽거나 쓰는 명령이 실행 된”것처럼 구현합니다. 구현이 지정 하면 쓸모없는 정의이지만 합법적 입니다. (아직 컴파일러에 대한 실제 사양을 찾지 못했습니다.

논쟁의 여지가 있지만 (그리고 내가 받아들이는 주장입니다) 이것은 하드웨어가 주소를 메모리 매핑 IO로 인식하고 재정렬 등을 금지하지 않는 한 표준의 의도를 위반합니다. 메모리 매핑 IO에 휘발성을 사용할 수도 없습니다. 적어도 Sparc 또는 Intel 아키텍처에서는. 그럼에도 불구하고 내가 본 어떤 컴파일러 (Sun CC, g ++ 및 MSC)도 펜스 또는 membar 명령을 출력하지 않습니다. (마이크로 소프트가
volatile. 그러나 내가 확인한 버전 (VS6.0 인 것 같음)은 펜스를 내 보내지 않았습니다.)