volatile(형 한정자)란?

C 언어의 형 한정자 중 하나로, 그 변수의 값이 프로그램이 보이지 않는 곳(주변 회로, 인터럽트 핸들러 등)에서 바뀌거나 읽기/쓰기 자체에 의미가 있다는 것을 컴파일러에 알린다. volatile을 붙인 객체에 대한 접근은 생략하거나 합치지 않고, 쓴 횟수와 순서 그대로 이루어진다. 메모리 맵 I/O의 레지스터나 인터럽트 핸들러와 공유하는 플래그에 사용한다. 조작을 불가분으로 만들거나 다른 코어와의 순서를 보장하는 기능은 없다.

일본어 명칭: volatile(型修飾子)

volatile(형 한정자)은(는) C 언어 기초, OS와 하드웨어, 멀티코어와 동기화 등 6개 분야의 연습 문제 7개에 나와요.

분야: C 언어 기초

붙이지 않으면 무슨 일이 일어나는가

while (flag == 0) { } 같은 루프에서 flag를 바꾸는 것이 인터럽트 핸들러뿐이면, 컴파일러는 루프 안에서 flag가 바뀌지 않는다고 판단하여 한 번만 읽고 무한 루프로 만들기도 합니다. 주변 회로의 상태 레지스터를 기다리는 루프에서도 같은 일이 일어나며, 같은 레지스터에 연속해서 쓰는 처리가 한 번으로 합쳐지기도 합니다. volatile을 붙이면 코드에 쓴 횟수만큼 실제로 읽고 씁니다.

선언 읽는 법

volatile uint32_t *p는 'volatile인 uint32_t를 가리키는 포인터'로, *p에 대한 접근이 생략되지 않게 됩니다. volatile uint32_t *const p로 하면 포인터 자체는 바꿀 수 없다(항상 같은 레지스터를 가리킨다)는 의미가 더해집니다. #define REG (*(volatile uint32_t *)0x40020014)처럼 주소를 포인터로 변환해서 참조하는 매크로도 자주 사용됩니다.

volatile로는 부족한 것

volatile은 읽기-수정-쓰기 조작을 불가분으로 만들지 않으므로, *reg |= mask;의 도중에 인터럽트가 들어가 다른 비트의 변경이 사라지는 경우가 있습니다. 8비트 마이컴에서의 32비트 변수처럼 한 번에 읽고 쓸 수 없는 크기의 값은, 도중에 인터럽트되면 일부만 새 값으로 읽힐 수 있습니다. 스레드 간 동기화에는 C11의 _Atomic이나 락처럼 불가분성과 순서를 보장하는 구조를 사용합니다.

도전해 보기: volatile(형 한정자) 관련 문제

C 언어에서 스레드 간에 주고받는 플래그 변수에 volatile을 붙이는 것만으로는 동기화로 불충분한 이유로 올바른 것은 무엇입니까?

  1. Avolatile을 붙인 변수는 다른 스레드에서 읽을 수 없게 되기 때문
  2. Bvolatile은 컴파일러의 최적화를 억제할 뿐이며, 연산의 불가분성이나 다른 코어에서 보이는 순서를 보장하지 않기 때문
  3. Cvolatile을 붙이면 변수가 디스크에 저장되어 느려지기 때문
정답과 해설 보기

정답: B. volatile은 컴파일러의 최적화를 억제할 뿐이며, 연산의 불가분성이나 다른 코어에서 보이는 순서를 보장하지 않기 때문

volatile은 변수의 읽기와 쓰기를 생략하거나 합치지 않도록 컴파일러에 지시하는 것으로, 원래는 하드웨어 레지스터 조작을 위한 것입니다. CPU가 메모리 연산의 순서를 바꾸는 것을 막을 수 없고, counter++와 같은 연산도 불가분해지지 않습니다. C11이나 C++11 이후에는 _Atomic이나 std::atomic을 사용하면 불가분성과 메모리 순서가 모두 보장됩니다.

자주 묻는 질문

const와 volatile을 동시에 붙일 수 있나요?

붙일 수 있습니다. const volatile은 '프로그램에서는 고쳐 쓰지 않지만, 외부 요인으로 바뀌므로 매번 다시 읽는다'는 의미로, 읽기 전용 상태 레지스터 등에 사용합니다.

volatile을 붙이면 느려지나요?

그 변수에 관해서는 값을 레지스터에 두는 최적화를 할 수 없게 되므로, 접근이 늘어나 느려지는 경우가 있습니다. 따라서 모든 변수에 붙이는 것이 아니라, 주변 회로의 레지스터나 인터럽트 핸들러와 공유하는 변수 등 필요한 것에만 붙입니다.

해설 속 volatile(형 한정자)

volatile(형 한정자)이(가) 나오는 문제 (7)

  1. 위와 같이 장치의 상태 레지스터를 메모리 맵 I/O로 읽을 때, volatile을 붙이고 그 영역을 캐시하지 않도록 설정하는 이유로 올바른 것은?
  2. C 언어에서 스레드 간에 주고받는 플래그 변수에 volatile을 붙이는 것만으로는 동기화로 불충분한 이유로 올바른 것은 무엇입니까?
  3. 마이컴 등에서 주변기기의 레지스터를 메모리와 같은 주소 공간에 할당하고 일반적인 메모리 읽기/쓰기 명령어로 조작하는 방식은?
  4. 위의 프로그램을 최적화를 켜고 컴파일하면 타이머 인터럽트가 발생해도 led_on()이 호출되지 않는 경우가 있습니다. 원인과 대책의 조합으로 올바른 것은 무엇입니까?
  5. 임베디드 프로그램에서 32비트 주변 장치 레지스터의 값을 넣는 변수를 unsigned int가 아니라 stdint.h의 uint32_t로 선언하는 경우가 많다. 주된 이유로 올바른 것은?
  6. 마이컴의 출력 레지스터를 위와 같이 선언하여 사용하고 있다. 이 선언에 대한 설명으로 올바른 것은?
  7. 위와 같은 작은 루프로 계산 속도를 측정했더니 N을 크게 해도 시간이 거의 0이었다. 가장 가능성이 높은 원인은?

관련 용어

변수(Variable) 메모리 맵 I/O(Memory-mapped I/O) 마이컴(마이크로컨트롤러) 인터럽트 핸들러 UART 인터럽트(Interrupt) 포인터(Pointer) 벤치마크(Benchmark) 공용체(union) 구조체(struct)

용어집으로 돌아가기