왜 휘발성이 존재합니까?


222

는 무엇을 않습니다 volatile할 키워드? C ++에서 어떤 문제가 해결됩니까?

제 경우에는 의도적으로 필요하지 않았습니다.


다음은 싱글 톤 패턴과 관련하여 휘발성에 대한 흥미로운 토론입니다. aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
chessguy

3
컴파일러가 volatile 키워드에 크게 의존 할 수있는 경쟁 조건을 감지하도록하는 흥미로운 기술이 있습니다 . http://www.ddj.com/cpp/184403766 에서 이에 대해 읽을 수 있습니다 .
Neno Ganchev

이것은 volatile효과적인 평신도 용어로 정리하여 효과적으로 사용할 수 있는 예를 보여주는 훌륭한 자료입니다 . 링크 : Publications.gbdirect.co.uk/c_book/chapter8/…
최적화 된 코더

잠금 해제 코드 / 더블 체크 잠금에 사용
paulm

저에게는 키워드 volatile보다 더 유용 friend합니다.
acegs

답변:


268

volatile 완전히 분리 된 프로세스 / 장치 / 무엇을 쓸 수 있는지 메모리의 특정 지점에서 읽는 경우 필요합니다.

나는 C에서 멀티 프로세서 시스템에서 듀얼 포트 램을 사용했습니다. 우리는 다른 사람이 언제 완료되었는지 알기 위해 세마포어로 16 비트 값으로 관리되는 하드웨어를 사용했습니다. 본질적으로 우리는 이것을했습니다 :

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

이 없으면 volatile옵티마이 저는 루프를 쓸모없는 것으로 간주합니다 (남자는 절대 값을 설정하지 않습니다! 그는 견과류를 제거하고 해당 코드를 제거하십시오!). 내 코드는 세마포어를 얻지 않고 계속 진행되어 나중에 문제가 발생합니다.


이 경우 uint16_t* volatile semPtr대신 작성 하면 어떻게됩니까? 이는 포인터 자체를 검사하는 등, 예를 들어 semPtr == SOME_ADDR최적화되지 않을 수 있도록 포인터를 휘발성으로 표시해야합니다 (예 : 값 대신) . 그러나 이것은 또한 휘발성 지적 가치를 다시 의미합니다. 아니?
Zyl

@Zyl No, 그렇지 않습니다. 실제로, 당신이 제안하는 것은 일어날 것입니다. 그러나 이론적으로는 값에 대한 액세스를 최적화하지 않는 컴파일러로 끝날 수 있습니다. 그리고 포인터가 아닌 값에 적용하기 위해 변동성을 의미한다면, 당신은 망할 것입니다. 다시 말하지만, 오늘날에도 발생하는 행동을 이용하는 것보다 올바른 일을 잘못 저지르는 것이 좋습니다.
iheanyi 2016 년

1
@Doug T. 더 나은 설명은 이것입니다
machineaddict

3
@ curiousguy 그것은 잘못 결정하지 않았습니다. 주어진 정보를 바탕으로 올바른 추론을했습니다. 휘발성을 표시하지 않으면 컴파일러는 휘발성아니라고 가정 할 수 있습니다. 그것이 코드를 최적화 할 때 컴파일러가하는 일입니다. 더 많은 정보, 즉 상기 데이터가 실제로 변동 적이라는 정보가있을 경우 해당 정보를 제공하는 것은 프로그래머의 책임입니다. 버그가 많은 컴파일러가 주장하는 것은 실제로 나쁜 프로그래밍입니다.
iheanyi

1
@curiousguy no, volatile 키워드가 한 번 나타나더라도 모든 것이 갑자기 휘발성이되는 것은 아닙니다. 컴파일러가 올바른 작업을 수행하고 프로그래머가 잘못 기대하는 것과 반대되는 결과를 얻는 시나리오를 제시했습니다. "가장 vexing 구문 분석"이 컴파일러 오류의 신호가 아니 듯이 여기서도 마찬가지입니다.
iheanyi

82

volatile내장 시스템 또는 장치 드라이버를 개발할 때 메모리 매핑 된 하드웨어 장치를 읽거나 써야합니다. 특정 장치 레지스터의 내용은 언제든지 변경 될 수 있으므로 volatile이러한 액세스가 컴파일러에 의해 최적화되지 않도록 키워드가 필요합니다 .


9
이것은 임베디드 시스템뿐만 아니라 모든 장치 드라이버 개발에 유효합니다.
Mladen Janković

내가 당신이 같은 주소를 두 번 읽는 8 비트 ISA 버스에서 그것을 필요로하는 유일한 시간 – 컴파일러는 버그를 가지고 그것을 무시했다 (초기 Zortech c ++)
Martin Beckett

휘발성은 외부 장치 제어에 거의 적합하지 않습니다. 현대 MMIO에서는 그 의미가 잘못되었습니다. 너무 많은 객체를 휘발성으로 만들어야하고 최적화가 손상됩니다. 그러나 최신 MMIO는 플래그가 설정되어 휘발성이 필요하지 않을 때까지 일반 메모리처럼 작동합니다. 많은 드라이버가 휘발성을 사용하지 않습니다.
curiousguy

69

일부 프로세서에는 64 비트 이상의 정밀도를 갖는 부동 소수점 레지스터가 있습니다 (예 : SSE가없는 32 비트 x86, Peter의 의견 참조). 이렇게하면 배정 밀도 숫자로 여러 연산을 실행하면 실제로 각 중간 결과를 64 비트로 자르는 것보다 더 정밀한 답변을 얻을 수 있습니다.

이것은 일반적으로 훌륭하지만 컴파일러가 레지스터를 할당하고 최적화를 수행 한 방법에 따라 정확히 동일한 입력에 대해 동일한 작업에 대해 다른 결과를 갖게됨을 의미합니다. 일관성이 필요한 경우 volatile 키워드를 사용하여 각 작업을 강제로 메모리로 되돌릴 수 있습니다.

대수적으로 이해하지 않지만 Kahan summation과 같은 부동 소수점 오류를 줄이는 일부 알고리즘에도 유용합니다. 대수적으로 그것은 nop이므로 일부 중간 변수가 휘발성이 아닌 한 종종 잘못 최적화됩니다.


5
수치 도함수를 계산할 때 x + h-x == h를 확인하기 위해 적절한 델타를 계산할 수 있도록 hh = x + h-x를 휘발성으로 정의합니다.
Alexandre C.

5
+1, 실제로는 부동 소수점 계산이 디버그 및 릴리스에서 다른 결과를 생성하는 경우가 있었으므로 한 구성에 대해 작성된 단위 테스트가 다른 구성에 실패했습니다. 추가 계산을 계속하기 전에 FPU 정밀도에서 64 비트 (RAM) 정밀도로 잘리는 것을 보장하기 위해 하나 volatile double대신 부동 소수점 변수를 선언하여이를 해결했습니다 double. 부동 소수점 오차의 추가 과장으로 인해 결과는 실질적으로 달랐습니다.
Serge Rogatch

"현대"에 대한 당신의 정의는 약간 벗어났습니다. SSE / SSE2를 피하는 32 비트 x86 코드 만 여기에 영향을 미치며 10 년 전에는 "현대화"되지 않았습니다. MIPS / ARM / POWER에는 모두 64 비트 하드웨어 레지스터가 있으며 x86에도 SSE2가 있습니다. C ++ 구현 x86-64는 항상 SSE2를 사용하며 컴파일러에는 g++ -mfpmath=sse32 비트 x86에도 사용하는 것과 같은 옵션이 있습니다 . 당신은 사용할 수 있습니다 gcc -ffloat-store반올림 강제로 모든 곳에서 x87을 사용하는 경우에도, 또는 53 비트 가수로 x87 정밀도를 설정할 수 있습니다 randomascii.wordpress.com/2012/03/21/...를 .
Peter Cordes

그러나 여전히 좋은 대답은 쓸모없는 x87 코드 생성기의 경우 volatile어디에서나 이점을 잃지 않고 몇 가지 특정 위치에서 반올림 하는 데 사용할 수 있습니다.
Peter Cordes

1
또는 부정확 한 내용과 부정확 한 내용을 혼동합니까?
Chipster

49

A로부터 "휘발성 약속으로" 단 삭스에 의해 기사 :

(...) 휘발성 개체는 값이 자연스럽게 변경 될 수있는 개체입니다. 즉, 객체를 휘발성으로 선언하면 프로그램의 명령문이 객체를 변경하는 것으로 보이지 않더라도 객체가 상태를 변경할 수 있음을 컴파일러에 알립니다. "

다음은 volatile키워드 에 관한 세 가지 기사의 링크입니다 .


23

잠금없는 데이터 구조를 구현할 때는 반드시 휘발성을 사용해야합니다. 그렇지 않으면 컴파일러는 변수에 대한 액세스를 자유롭게 최적화하여 의미를 변경합니다.

달리 말하면, volatile은이 변수에 액세스하는 컴파일러가 실제 메모리 읽기 / 쓰기 작업에 해당해야한다는 것을 컴파일러에 알려줍니다.

예를 들어, Win32 API에서 InterlockedIncrement를 선언하는 방법은 다음과 같습니다.

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

InterlockedIncrement를 사용하기 위해 변수를 휘발성으로 선언 할 필요는 없습니다.
curiousguy

이 답변은 이제 C ++ 11이 제공 std::atomic<LONG>하므로 더 이상 사용되지 않으므로 순수한로드 / 순수한 상점을 최적화하거나 재정렬 또는 기타 다른 문제없이 잠금없는 코드를보다 안전하게 작성할 수 있습니다.
Peter Cordes

10

1990 년대 초에 작업했던 대규모 응용 프로그램에는 setjmp 및 longjmp를 사용하는 C 기반 예외 처리가 포함되었습니다. 휘발성 키워드는 "catch"절 역할을하는 코드 블록에 값을 유지해야하는 변수에 필요했습니다. 이러한 변수는 레지스터에 저장되고 longjmp에 의해 삭제되지 않습니다.


10

표준 C에서 사용할 장소 중 하나 volatile는 신호 처리기입니다. 실제로 표준 C에서 신호 처리기에서 안전하게 할 수있는 모든 것은 volatile sig_atomic_t변수를 수정 하거나 빠르게 종료하는 것입니다. 실제로 AFAIK는 표준 C에서 volatile정의되지 않은 동작을 피하기 위해 사용해야하는 유일한 장소입니다 .

ISO / IEC 9899 : 2011 §7.14.1.1 signal기능

¶5 abort또는 raise함수 호출의 결과가 아닌 다른 신호가 발생하는 경우, 신호 핸들러가 값을 지정하는 것 이외의 잠금없는 원자 오브젝트가 아닌 정적 또는 스레드 저장 기간을 갖는 오브젝트를 참조하면 동작이 정의되지 않습니다. 로 선언 된 객체 volatile sig_atomic_t에 신호 를 보내 거나 신호 핸들러는 표준 라이브러리에서 abort함수, _Exit함수, quick_exit함수 또는 signal첫 번째 인수 가 함수를 호출하는 원인이되는 신호에 해당하는 신호 번호와 다른 함수 이외의 함수를 호출합니다. 매니저. 또한 이러한 signal함수 호출로 인해 SIG_ERR 리턴이 발생하면 값 errno이 결정되지 않습니다. 252)

252) 비동기 신호 처리기에서 신호를 생성하면 동작이 정의되지 않습니다.

이는 표준 C에서 다음을 작성할 수 있음을 의미합니다.

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

그다지 많지 않습니다.

POSIX는 신호 처리기에서 수행 할 수있는 작업에 대해 훨씬 관대하지만 여전히 제한 사항이 있습니다 (한 가지 제한 사항 중 하나는 표준 I / O 라이브러리 (예 : printf()기타)는 안전하게 사용할 수 없음).


7

임베디드를 개발하기 위해 인터럽트 핸들러에서 변경할 수있는 변수를 확인하는 루프가 있습니다. "휘발성"이 없으면 루프가 스눕이됩니다. 컴파일러가 알 수있는 한 변수는 절대 변경되지 않으므로 검사가 최적화됩니다.

더 전통적인 환경의 다른 스레드에서 변경 될 수있는 변수에도 동일한 내용이 적용되지만 동기화 호출을 수행하는 경우가 많으므로 컴파일러에는 최적화가 자유롭지 않습니다.


7

컴파일러가 코드를 단계별로 볼 때보 고 싶은 변수를 최적화해야한다고 주장 할 때 디버그 빌드에서 사용했습니다.


7

의도 한대로 사용하는 것 외에도 휘발성은 (템플릿) 메타 프로그래밍에 사용됩니다. const와 같은 휘발성 속성이 과부하 해결에 참여하므로 우발적 인 과부하를 방지하는 데 사용할 수 있습니다.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

이것은 합법적입니다. 두 과부하 모두 호출 가능하고 거의 동일합니다. volatile우리는 바가 비 휘발성을 통과하지 않는다는 것을 알고 있으므로 과부하 의 캐스트 는 합법적 T입니다. 그러나 volatile버전은 엄청나게 나쁘므로 비 휘발성 f을 사용할 수있는 경우 과부하 해결에서 선택하지 마십시오 .

코드는 실제로 volatile메모리 액세스에 의존하지 않습니다 .


예를 들어 이것에 대해 자세히 설명해 주시겠습니까? 정말 잘 이해하는 데 도움이 될 것입니다. 감사!
batbrat

" 휘발성 과부하 의 캐스트"캐스트는 명시 적 변환입니다. SYNTAX 구문입니다. 많은 사람들이 그러한 혼란을 겪습니다 (표준 저자조차도).
curiousguy

6
  1. spinlocks 및 일부 (모든?) lock-free 데이터 구조를 구현하는 데 사용해야합니다.
  2. 원자 작업 / 지침과 함께 사용하십시오.
  3. 컴파일러의 버그를 극복하는 데 도움이되었습니다 (최적화 중에 잘못 생성 된 코드)

5
라이브러리, 컴파일러 내장 함수 또는 인라인 어셈블리 코드를 사용하는 것이 좋습니다. 휘발성은 신뢰할 수 없습니다.
Zan Lynx

1
1과 2는 모두 원자 연산을 사용하지만 휘발성은 원자 의미를 제공하지 않으며 원자의 플랫폼 별 구현은 휘발성을 사용해야하는 필요성을 대체하므로 1과 2에 대해서는 동의하지 않습니다.

휘발성 원자 적 의미를 제공하는 것에 대해 누가 말합니까? 난 당신이 원자 운영과 휘발성 사용할 필요가 말했다 당신이는 Win32 API의 연동 작업의 선언에 그것의 진정한 모습 (이 사람도 그의 대답이 설명) 생각하지 않는 경우
믈라덴 얀코비치

4

volatile키워드는 컴파일러에 의해 결정될 수없는 방식으로 변경할 수있는 객체에 어떤 최적화를 적용에서 컴파일러를 방지하기위한 것입니다.

로 선언 된 객체는 volatile현재 코드 범위를 벗어난 코드로 언제든지 값을 변경할 수 있으므로 최적화에서 생략됩니다. volatile이전 명령이 동일한 객체의 값을 요청한 경우에도 시스템은 요청 된 시점에 임시 레지스터에 값을 유지하지 않고 항상 메모리 위치에서 객체 의 현재 값을 읽습니다 .

다음과 같은 경우를 고려하십시오

1) 범위 밖의 인터럽트 서비스 루틴에 의해 수정 된 글로벌 변수.

2) 멀티 스레드 응용 프로그램 내의 전역 변수.

휘발성 한정자를 사용하지 않으면 다음과 같은 문제가 발생할 수 있습니다.

1) 최적화를 설정하면 코드가 예상대로 작동하지 않을 수 있습니다.

2) 인터럽트가 활성화되고 사용될 때 코드가 예상대로 작동하지 않을 수 있습니다.

휘발성 : 프로그래머의 가장 친한 친구

https://ko.wikipedia.org/wiki/Volatile_(computer_programming)


게시 한 링크가 매우 구식이며 현재 모범 사례를 반영하지 않습니다.
Tim Seguine

2

휘발성 키워드가 일부 변수에 대한 액세스를 (즉,이 스레드 또는 인터럽트 루틴에 의해 변경 될 수 있습니다) 최적화되지 컴파일러 말에 사용된다는 사실 게다가, 또한 수 있습니다 일부 컴파일러 버그를 제거하는 데 사용 - YES가 수 --- 이어야합니다 .

예를 들어 컴파일러가 변수 값과 관련하여 잘못된 가정을하고 있었기 때문에 임베디드 플랫폼에서 작업했습니다. 코드가 최적화되지 않은 경우 프로그램이 정상적으로 실행됩니다. 최적화 (필수 루틴이기 때문에 실제로 필요)를 사용하면 코드가 제대로 작동하지 않습니다. (정확하지는 않지만) 유일한 해결책은 'faulty'변수를 휘발성으로 선언하는 것입니다.


3
컴파일러가 휘발성 물질에 대한 액세스를 최적화하지 않는다는 생각은 잘못된 가정입니다. 표준은 최적화에 대해 아무것도 모릅니다. 컴파일러는 표준이 지시하는 것을 존중해야하지만 정상적인 동작을 방해하지 않는 최적화는 자유롭게 수행 할 수 있습니다.
Terminus

3
내 경험으로는 gcc arm의 모든 최적화 "버그"의 99.9 %가 프로그래머의 오류입니다. 이것이이 답변에 적용되는지 전혀 모른다. 일반적인 주제에 대한
열성

@Terminus " 컴파일러가 휘발성 물질에 대한 액세스를 최적화하지 않는다는 생각은 잘못된 가정입니다. "
curiousguy

2

volatile키워드 없이도 프로그램이 작동하는 것 같 습니까? 아마도 이것이 이유입니다.

앞에서 언급했듯이 volatile키워드는 다음과 같은 경우에 도움이됩니다.

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

그러나 외부 또는 인라인이 아닌 함수가 호출되면 거의 영향을 미치지 않는 것 같습니다. 예 :

while( *p!=0 ) { g(); }

그런 다음 volatile거의 동일한 결과가 있거나없는 결과가 생성됩니다.

g ()가 완전히 인라인 될 수있는 한, 컴파일러는 진행중인 모든 것을보고 따라서 최적화 할 수 있습니다. 그러나 프로그램이 컴파일러가 무슨 일이 일어나고 있는지 볼 수없는 곳을 호출하면 컴파일러가 더 이상 가정을하는 것이 안전하지 않습니다. 따라서 컴파일러는 항상 메모리에서 직접 읽는 코드를 생성합니다.

그러나 함수 g ()가 인라인이 될 때 (명시적인 변경이나 컴파일러 / 링커 영리함으로 인해) volatile키워드 를 잊어 버린 경우 코드가 손상 될 수 있습니다 .

따라서 volatile프로그램이 없어도 키워드 를 추가하는 것이 좋습니다 . 향후 변경과 관련하여 의도를보다 명확하고 강력하게 만듭니다.


함수는 개요 함수에 대한 참조 (링크 타임에 해결됨)를 생성하면서 코드를 인라인 할 수 있습니다. 이것은 부분적으로 인라인 된 재귀 함수의 경우입니다. 함수는 또한 컴파일러에 의해 "인라인 된"의미를 가질 수 있습니다. 즉, 컴파일러는 부작용과 결과가 소스 코드에 따라 가능한 부작용 및 결과 내에 있다고 가정하지만 여전히 인라인하지는 않습니다. 이것은 "유효한 하나의 정의 규칙"을 기반으로하며 엔티티의 모든 정의는 실질적으로 동일해야합니다 (정확히 동일하지 않은 경우).
curiousguy 2019 년

컴파일러가 본문을 볼 수있는 함수 (글로벌 최적화와의 링크 시간에서도)에 의해 호출의 인라인 (또는 의미의 "인라인")을 volatilevoid (* volatile fun_ptr)() = fun; fun_ptr();
피할 수있는 것은

2

C의 초기에는 컴파일러가 lvalue를 읽고 쓰는 모든 작업을 메모리 작업으로 해석하여 코드에 나타난 읽기 및 쓰기와 동일한 순서로 수행됩니다. 컴파일러에 연산 순서를 재정렬하고 통합 할 수있는 자유가 주어진 경우 많은 경우 효율성이 크게 향상 될 수 있지만 이에 문제가있었습니다. 심지어 작업은 그들을 지정할 필요했다 단지 때문에 종종 특정 순서로 지정된 일부 순서, 따라서 프로그래머는 항상 그런 것은 아니었다 그 많은 동등하게-좋은 대안 중 하나를 골랐다. 때로는 특정 작업이 특정 순서로 발생하는 것이 중요합니다.

시퀀싱에 대한 정확한 세부 사항은 대상 플랫폼 및 응용 분야에 따라 다릅니다. 표준은 특히 세부적인 제어를 제공하기보다는 간단한 모델을 선택했습니다. 일련의 액세스가 규정되지 않은 lvalue로 수행되는 volatile경우, 컴파일러는 적합하다고 판단 될 때이를 재정렬하고 통합 할 수 있습니다. - volatile정규화 된 lvalue로 조치를 수행하는 경우 , 품질 구현은 비표준 구문을 사용할 필요없이 의도 된 플랫폼 및 애플리케이션 필드를 대상으로하는 코드에 의해 필요한 추가 주문 보장을 제공해야합니다.

불행히도 프로그래머가 필요로하는 것이 무엇인지 확인하는 대신 많은 컴파일러가 표준에 의해 요구되는 최소한의 보증을 제공하기로 선택했습니다. 이것은 필요한 volatile것보다 훨씬 덜 유용합니다. 예를 들어 gcc 또는 clang에서 기본 "핸드 오프 뮤텍스"를 구현해야하는 프로그래머 ([뮤텍스를 획득하고 릴리스 한 작업이 다른 작업을 수행 할 때까지 다시 수행하지 않는 경우]) 하나를 수행해야합니다. 네 가지 중 :

  1. 컴파일러가 인라인 할 수없고 전체 프로그램 최적화를 적용 할 수없는 함수에 뮤텍스의 획득 및 릴리스를 배치하십시오.

  2. 뮤텍스에 의해 보호되는 모든 객체를 뮤텍스를 volatile획득 한 후 릴리스하기 전에 모든 액세스가 발생하는 경우 필요하지 않은 것으로 규정하십시오.

  3. 정규화되지 않은 모든 객체는 것처럼 코드를 생성하는 컴파일러를 강제로 최적화 레벨 0을 사용하여 register있습니다 volatile.

  4. gcc 특정 지시문을 사용하십시오.

반대로, icc와 같은 시스템 프로그래밍에 더 적합한 고품질 컴파일러를 사용하는 경우 다른 옵션이 있습니다.

  1. volatile획득 또는 릴리스가 필요한 모든 장소 에서 -qualified 쓰기가 수행 되는지 확인하십시오 .

기본 "hand-off mutex"를 얻으 volatile려면 읽기 가 필요하고 (준비되었는지 확인하기 위해) volatile쓰기가 필요하지 않아야합니다 (다른 쪽은 다시 넘길 때까지 다시 획득하려고 시도하지 않음). 의미없는 volatile쓰기를 수행하는 것은 gcc 또는 clang에서 사용 가능한 옵션보다 여전히 낫습니다.


1

신호 처리기 함수에서 전역 변수에 액세스 / 수정하려는 경우 (예 : 종료 = true로 표시) 해당 변수를 '휘발성'으로 선언해야합니다.


1

모든 답변이 우수합니다. 그러나 그 위에 모범을 보이고 싶습니다.

아래는 약간의 cpp 프로그램입니다 :

#include <iostream>

int x;

int main(){
    char buf[50];
    x = 8;

    if(x == 8)
        printf("x is 8\n");
    else
        sprintf(buf, "x is not 8\n");

    x=1000;
    while(x > 5)
        x--;
    return 0;
}

이제 위 코드의 어셈블리를 생성 해 보겠습니다 (여기서 관련된 부분 만 붙여 넣겠습니다).

어셈블리를 생성하는 명령 :

g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp

그리고 어셈블리 :

main:
.LFB1594:
    subq    $40, %rsp    #,
    .seh_stackalloc 40
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC0(%rip), %rcx     #,
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:10:         printf("x is 8\n");
    call    _ZL6printfPKcz.constprop.0   #
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    movl    $5, x(%rip)  #, x
    addq    $40, %rsp    #,
    ret 
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

sprintf컴파일러 x는 프로그램 외부에서 변경되지 않는다고 가정했기 때문에 어셈블리 코드가 생성되지 않았 음을 어셈블리에서 볼 수 있습니다 . while루프도 마찬가지입니다 . while컴파일러는 쓸모없는 코드로보고, 따라서 직접 할당하기 때문에 루프는 모두 인해 최적화에 제거 5x(참조 movl $5, x(%rip)).

외부 프로세스 / 하드웨어가 와 x사이 의 값을 변경하면 어떻게 됩니까 ? 블록이 작동 할 것으로 예상 되지만 불행히도 컴파일러는 해당 부분을 제거했습니다.x = 8;if(x == 8)else

자,에,이 문제를 해결하기 위해 assembly.cpp, 우리가 변경할 수 int x;volatile int x;빠르게 생성 된 어셈블리 코드를 참조하십시오

main:
.LFB1594:
    subq    $104, %rsp   #,
    .seh_stackalloc 104
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:9:     if(x == 8)
    movl    x(%rip), %eax    # x, x.1_1
 # assembly.cpp:9:     if(x == 8)
    cmpl    $8, %eax     #, x.1_1
    je  .L11     #,
 # assembly.cpp:12:         sprintf(buf, "x is not 8\n");
    leaq    32(%rsp), %rcx   #, tmp93
    leaq    .LC0(%rip), %rdx     #,
    call    _ZL7sprintfPcPKcz.constprop.0    #
.L7:
 # assembly.cpp:14:     x=1000;
    movl    $1000, x(%rip)   #, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_15
    cmpl    $5, %eax     #, x.3_15
    jle .L8  #,
    .p2align 4,,10
.L9:
 # assembly.cpp:16:         x--;
    movl    x(%rip), %eax    # x, x.4_3
    subl    $1, %eax     #, _4
    movl    %eax, x(%rip)    # _4, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_2
    cmpl    $5, %eax     #, x.3_2
    jg  .L9  #,
.L8:
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    addq    $104, %rsp   #,
    ret 
.L11:
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC1(%rip), %rcx     #,
    call    _ZL6printfPKcz.constprop.1   #
    jmp .L7  #
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

여기에서 조립에 대한 코드를 볼 수 있습니다 sprintf, printf그리고 while루프가 생성되었다. x외부 프로그램이나 하드웨어에 의해 변수가 변경 sprintf되면 코드의 일부가 실행 된다는 이점이 있습니다. 그리고 유사하게 while루프는 이제 바쁜 대기를 위해 사용될 수 있습니다.


0

다른 답변은 이미 다음과 같은 목적으로 일부 최적화를 피한다고 언급했습니다.

  • 메모리 매핑 레지스터 (또는 "MMIO") 사용
  • 장치 드라이버 작성
  • 프로그램을보다 쉽게 ​​디버깅 할 수 있습니다
  • 부동 소수점 계산을보다 결정 론적으로 만듭니다.

휘발성은 외부에서 나오고 예측할 수없는 것처럼 보일 필요가 있고 알려진 값을 기준으로 컴파일러 최적화를 피하고 결과가 실제로 사용되지 않지만 계산되거나 사용되어야하는 경우에는 필수적입니다. 벤치 마크를 위해 여러 번 계산하고 정확한 지점에서 시작하고 종료하려면 계산이 필요합니다.

휘발성 읽기는 입력 작업 (유사 scanf또는 사용 cin) 과 유사 합니다. 값은 프로그램 외부에서 온 것으로 보이 므로 값에 종속 된 계산은 그 이후에 시작해야합니다 .

휘발성 쓰기는 출력 작업과 유사합니다 ( printf의 사용 또는 사용 cout). 값은 프로그램 외부에서 전달되는 것 같습니다 . 따라서 값이 계산에 의존하는 경우 이전에 완료해야 합니다.

따라서 한 쌍의 휘발성 읽기 / 쓰기를 사용하여 벤치 마크를 길들이고 시간 측정을 의미있게 할 수 있습니다 .

휘발성이 없으면 컴파일러에서 계산을 시작할 수 있습니다 . 시간 측정과 같은 함수를 사용하여 계산 순서를 변경하는 것은 없습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.