의 정의 volatile
volatile
컴파일러에 알리지 않고 변수 값이 변경 될 수 있음을 컴파일러에 알립니다. 따라서 컴파일러는 C 프로그램이 값을 변경하지 않은 것만으로 값이 변경되지 않았다고 가정 할 수 없습니다.
반면에, 컴파일러가 알지 못하는 곳에서는 변수의 값이 필요 (읽기) 될 수 있으므로 변수에 대한 모든 할당이 실제로 쓰기 작업으로 수행되도록해야합니다.
사용 사례
volatile
필요한 경우
- 하드웨어 레지스터 (또는 메모리 매핑 된 I / O)를 변수로 나타냄-레지스터를 읽지 않더라도 컴파일러는 "Stupid programmer. 다시 읽지 않을 것입니다. 글을 생략해도 눈치 채지 못할 것입니다. " 반대로, 프로그램이 변수에 값을 쓰지 않더라도 하드웨어에 의해 값이 변경 될 수 있습니다.
- 실행 컨텍스트간에 변수 공유 (예 : ISR / 메인 프로그램) (@ kkramo의 답변 참조)
의 효과 volatile
변수가 선언 volatile
되면 컴파일러는 프로그램 코드에서 변수에 대한 모든 할당이 실제 쓰기 작업에 반영되고 프로그램 코드에서 읽을 때마다 (매핑 된) 메모리에서 값을 읽도록해야합니다.
비 휘발성 변수의 경우, 컴파일러는 변수의 값이 변경되는시기와시기를 알고 다른 방식으로 코드를 최적화 할 수 있다고 가정합니다.
하나의 경우, 컴파일러는 CPU 레지스터에 값을 유지함으로써 메모리에 대한 읽기 / 쓰기 수를 줄일 수 있습니다.
예:
void uint8_t compute(uint8_t input) {
uint8_t result = input + 2;
result = result * 2;
if ( result > 100 ) {
result -= 100;
}
return result;
}
여기서 컴파일러는 아마도 result
변수에 RAM을 할당하지 않을 것이고 중간 값을 CPU 레지스터 이외의 어디에도 저장하지 않을 것입니다.
result
일시적인 경우 result
C 코드에서 발생 하면 컴파일러가 RAM (또는 I / O 포트)에 대한 액세스를 수행해야하므로 성능이 저하됩니다.
둘째, 컴파일러는 성능 및 / 또는 코드 크기를 위해 비 휘발성 변수에 대한 연산을 재정렬 할 수 있습니다. 간단한 예 :
int a = 99;
int b = 1;
int c = 99;
에 재주문 가능
int a = 99;
int c = 99;
int b = 1;
값 99
을 두 번로드 할 필요가 없기 때문에 어셈블러 명령어를 저장할 수 있습니다 .
경우 a
, b
그리고 c
휘발성 있었다 컴파일러는이 프로그램에 주어진대로 정확한 순서로 값을 할당 지시를 방출해야합니다.
다른 고전적인 예는 다음과 같습니다.
volatile uint8_t signal;
void waitForSignal() {
while ( signal == 0 ) {
// Do nothing.
}
}
이 경우에 컴파일러 signal
가 아닌 경우, volatile
컴파일러는 while( signal == 0 )
무한 루프 일 수있는 '생각'하고 ( 루프 내부의signal
코드에 의해 절대 변경되지 않기 때문에 )
void waitForSignal() {
if ( signal != 0 ) {
return;
} else {
while(true) { // <-- Endless loop!
// do nothing.
}
}
}
신중한 volatile
값 처리
위에서 언급했듯이 volatile
변수는 실제로 필요한 것보다 더 자주 액세스 할 때 성능 저하를 초래할 수 있습니다. 이 문제를 완화하기 위해 다음과 같이 비 휘발성 변수에 할당하여 값을 "비 휘발성"할 수 있습니다.
volatile uint32_t sysTickCount;
void doSysTick() {
uint32_t ticks = sysTickCount; // A single read access to sysTickCount
ticks = ticks + 1;
setLEDState( ticks < 500000L );
if ( ticks >= 1000000L ) {
ticks = 0;
}
sysTickCount = ticks; // A single write access to volatile sysTickCount
}
당신이 빠른으로되고 싶은 곳 ISR의에서 특히 도움이 될 수 있습니다 가능한 경우 동일한 하드웨어 또는 메모리를 여러 번 액세스 할 수 없습니다 당신이 값이 ISR이 실행되는 동안 변경되지 않기 때문에 필요하지 않은 것을 알고있다. 이는 sysTickCount
위의 예 와 같이 ISR이 변수 값의 '생산자'인 경우에 일반적 입니다. AVR에서는 함수 doSysTick()
가 메모리에서 동일한 4 바이트 (4 개의 명령 = 액세스 당 8 개의 CPU 사이클 sysTickCount
)에 두 번이 아니라 5 번 또는 6 번 액세스하는 것이 특히 고통 스럽습니다 . 프로그래머는 값이 doSysTick()
실행 중에 다른 코드에서 변경 될 수 있습니다 .
이 트릭을 사용하면 본질적으로 컴파일러가 비 휘발성 변수에 대해 수행하는 것과 똑같은 작업을 수행합니다. 즉, 필요할 때만 메모리에서 변수를 읽고 레지스터에 값을 일정 시간 유지하고 필요할 때만 메모리에 다시 씁니다. ; 하지만 이번에는 당신이 읽을 때 / 쓰기가있는 경우 / 더 나은 컴파일러보다 더 알고 있어야 일이 최적화 작업에서 컴파일러를 완화 있도록하고, 그것을 스스로 할.
의 한계 volatile
비 원자 액세스
volatile
다중 단어 변수에 원자 적 액세스를 제공 하지 않습니다 . 이러한 경우를 위해, 당신은 다른 방법으로 상호 배제를 제공해야합니다 추가로 사용하는 volatile
. AVR의에, 당신은 사용할 수 있습니다 ATOMIC_BLOCK
에서 <util/atomic.h>
또는 간단한 cli(); ... sei();
통화. 각각의 매크로는 메모리 장벽 역할도하므로 액세스 순서에있어 중요합니다.
실행 순서
volatile
다른 휘발성 변수에 대해서만 엄격한 실행 순서를 부과합니다. 예를 들어
volatile int i;
volatile int j;
int a;
...
i = 1;
a = 99;
j = 2;
에 보장 처음 에 할당 한 i
및 다음 2에 할당 j
. 그러나 사이에 할당되는 것은 보장 되지 않습니다a
. 컴파일러는 코드 스 니펫 이전 또는 이후에 해당 할당을 수행 할 수 있습니다 a
.
위에서 언급 한 매크로의 메모리 장벽이 아닌 경우 컴파일러는 번역 할 수 있습니다
uint32_t x;
cli();
x = volatileVar;
sei();
에
x = volatileVar;
cli();
sei();
또는
cli();
sei();
x = volatileVar;
(완전성을 위해 모든 액세스가 이러한 장벽으로 묶여 volatile
있으면 sei / cli 매크로에 의해 암시 된 것과 같은 메모리 장벽이 실제로 사용을 제거 할 수 있다고 말해야합니다 .)