it-swarm-ko.tech

왜 휘발성이 존재합니까?

volatile 키워드의 기능은 무엇입니까? C++에서 어떤 문제가 해결됩니까?

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

201
theschmitzer

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이 없으면 옵티마이 저는 루프를 쓸모없는 것으로 간주합니다 (남자는 값을 설정하지 않습니다! 그는 견과류를 제거하고 코드를 제거하십시오!). 세마포어를 얻지 않고 코드가 진행되어 나중에 문제가 발생합니다.

247
Doug T.

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

78
ChrisN

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

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

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

68
tfinniga

Dan Saks의 "Volatile as a promise" 기사에서 :

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

volatile 키워드에 관한 그의 기사 중 3 개의 링크는 다음과 같습니다.

45
MikeZ

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

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

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

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);
23

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

10
Jeff Doar

표준 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() et al-를 안전하게 사용할 수 없다는 것입니다. ).

10
Jonathan Leffler

의도 한대로 사용하는 것 외에도 휘발성은 (템플릿) 메타 프로그래밍에 사용됩니다. 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 메모리 액세스에 의존하지 않습니다.

7
MSalters

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

7
indentation

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

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

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

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

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

다음 경우를 고려하십시오

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

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

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

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

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

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

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

4
roottraveller

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 키워드를 추가하는 것이 좋습니다. 향후 변경과 관련하여 의도를보다 명확하고 강력하게 만듭니다.

2
Joachim

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

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

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

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

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

  3. 정규화되지 않은 register가 아닌 모든 객체가 volatile 인 것처럼 컴파일러가 코드를 생성하도록하려면 최적화 수준 0을 사용하십시오.

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

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

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

기본 "hand-off mutex"를 얻으려면 volatile 읽기 (준비 된지 확인)가 필요하며 volatile 쓰기도 필요하지 않습니다 (다른 쪽은 시도하지 않습니다) 되돌릴 때까지 다시 가져 오십시오.) 의미없는 volatile 쓰기를 수행하는 것이 gcc 또는 clang에서 사용 가능한 옵션보다 여전히 낫습니다.

2
supercat

Volatile 키워드는 컴파일러가 스레드 또는 인터럽트 루틴에 의해 수정 될 수있는 일부 변수에 대한 액세스를 최적화하지 않도록 지시하는 데 사용된다는 사실 외에도 일부 컴파일러 버그를 제거하는 데 사용- 예 그렇습니다 ---.

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

2
INS

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

1
bugs king