본문 바로가기
C 언어/실무

C언어 volatile은 언제 써야 하는가?

by Autosar 2026. 4. 11.
반응형

C언어를 공부하다 보면 volatile 키워드를 한 번쯤 접하게 된다.
하지만 많은 경우 “컴파일러 최적화를 막기 위한 키워드” 정도로만 이해하고 넘어가곤 한다.

이 설명이 틀린 것은 아니지만, 이 정도 수준으로 이해하고 실제 개발에 적용하면
특정 상황에서 코드가 정상적으로 동작하지 않는 문제를 만들 수 있다.

특히 임베디드 시스템, 인터럽트, AUTOSAR 환경에서는

volatile의 역할을 정확히 이해하는 것이 매우 중요하다.

이 글에서는 volatile이 왜 필요한지, 언제 사용해야 하는지, 그리고 잘못 사용하면 어떤 문제가 발생하는지를
실제 코드 예제 중심으로 정리한다.

 

volatile이란 무엇인가?

 

volatile은 컴파일러에게 다음과 같은 의미를 전달하는 키워드이다.
“이 변수의 값은 프로그램 흐름 외부에서 언제든지 변경될 수 있다.”

여기서 “외부”란 단순히 다른 코드가 아니라 다음과 같은 상황을 포함한다.
인터럽트, 다른 task 또는 쓰레드, 하드웨어 레지스터 (MMIO) 등등..

이렇게 값이 변경될 수 있는 변수에 대해, 컴파일러는 다음과 같은 최적화를 하면 안 된다.

- 값을 레지스터에 캐싱해서 재사용
- 불필요하다고 판단하고 메모리 접근 생략

즉, volatile이 붙은 변수는 항상 메모리에서 직접 값을 읽고 써야 한다.

 

왜 문제가 발생하는가 (최적화 관점 이해)

 

컴파일러는 프로그램 성능을 높이기 위해 다양한 최적화를 수행한다.
그 중 하나가 “값이 변하지 않는 변수는 반복해서 메모리에서 읽지 않는다”는 것이다.

아래 코드를 보자.

int flag = 0;

while (flag == 0)
{
    // 대기
}

 

사람이 보기에는 단순히 flag 값이 1로 바뀌기를 기다리는 코드다.
하지만 컴파일러 입장에서는 다음과 같이 판단할 수 있다.

- flag 값이 이 코드 내부에서 변경되지 않는다.
- 따라서 한 번 읽은 값은 계속 동일하다.

이 판단이 적용되면, 실제로는 다음과 같은 형태로 최적화될 가능성이 있다.

while (1)
{
    // 무한 루프
}

 

즉, 외부에서 flag 값을 변경해도 루프를 빠져나오지 못하는 상황이 발생할 수 있다.
이 문제를 방지하기 위해 사용하는 것이 바로 volatile이다.

 

실무 활용 가이드

 

1) 하드웨어 레지스터 접근 (MMIO)

 

임베디드에서 volatile이 필수인 대표적인 상황이다.

하드웨어 레지스터는 특정 메모리 주소에 매핑되어 있으며,
이 값은 CPU가 아닌 외부 하드웨어에 의해 변경된다.

잘못된 코드

#define STATUS_REG (*(unsigned int*)0x40000000)

while ((STATUS_REG & 0x01) == 0)
{
    // 상태 변화 대기
}


이 경우 컴파일러는 STATUS_REG 값을 고정된 값으로 판단할 수 있고,
결과적으로 루프가 정상적으로 동작하지 않을 수 있다.

 

올바른 코드

#define STATUS_REG (*(volatile unsigned int*)0x40000000)

while ((STATUS_REG & 0x01) == 0)
{
    // 실제 하드웨어 상태를 지속적으로 확인
}

 

하드웨어 레지스터는 항상 volatile로 선언하는 것이 기본이다.

2) Task 간 공유 변수 (AUTOSAR)

 

멀티태스킹 환경에서도 동일한 문제가 발생할 수 있다.

잘못된 코드

int sharedData = 0;

void Task1(void)
{
    while (sharedData == 0)
    {
        // 대기
    }
}

void Task2(void)
{
    sharedData = 1;
}

 

이 경우에도 컴파일러는 sharedData 값이 변하지 않는다고 판단할 수 있다.

 

올바른 코드

volatile int sharedData = 0;

 

다른 실행 흐름(태스크)이 값을 변경하는 경우에는 반드시 고려해야 한다.

 

volatile 사용 기준 (실무 핵심 정리)

 

실무에서는 다음 3가지 상황에서 volatile 사용을 반드시 고려해야 한다.

1. 인터럽트와 공유되는 변수 (ISR ↔ Main)
2. 하드웨어 레지스터 접근 (MMIO)
3. 태스크/쓰레드 간 공유 변수

이 세 가지는 거의 “필수 사용 케이스”라고 봐도 된다.

 

정리

 

volatile을 한 문장으로 정리하면 다음과 같다.

“컴파일러에게 이 변수는 항상 다시 읽어야 한다고 알려주는 키워드”

이 키워드를 사용하지 않으면 다음과 같은 문제가 발생할 수 있다.
- 인터럽트에서 값이 변경되어도 감지하지 못함
- 하드웨어 레지스터 값을 제대로 읽지 못함
- 태스크 간 데이터 공유 오류 발생

 

마지막으로 가장 중요한 기준은 이것 하나다.


“이 변수의 값이 프로그램 흐름 외부에서 변경될 가능성이 있는가?”

YES라면 → volatile을 사용해야 한다.

반응형