[C, C++] volatile 변수의 필요성

volatile?

C언어의 컴파일러는 똑똑해서 자동으로 코드 최적화를 해주는 기능을 가지고 있다. 예를 들어 아래의 코드에서 for문은 의미가 없는 코드이다. for문 내에 어떠한 내용도 없기 때문이다. 따라서 시간만 잡아먹는 이 의미 없는 for문을 컴파일러가 자동으로 지워버린다.

int main(){
    for(int i=0;i<100000;i++); //이 라인은 실행되지 않음
}

그러나 volatile과 함께라면 컴파일러는 의미없는 for문이라도 최적화를 하지 않고 실행한다.

int main(){
    for(volatile int i=0;i<100000;i++); //이 라인은 실행 됨
}

위에서 C언어의 컴파일러는 자동으로 최적화를 한다고 했는데, 이는 컴파일러의 최적화 옵션에 따라 다르다. 그러나 한 번도 최적화 옵션을 사용해본 적은 없는데, 컴파일러 최적화 옵션 대신 volatile을 사용하면 되기 때문이다. (그러나 사실 volatile도 사용해본 경험이 없다. 그래도 알아는 둬야한다.)

volatile 기능

volatile은 아래와 같은 기능을 한다.

  • 필요없는 코드의 제거
  • 변수의 순서를 고정
  • 캐싱된 데이터를 읽어오지 않음

이 기능 중 필요 없는 코드의 제거는 위에서 설명했고 가장 핵심은 캐싱된 데이터를 읽어오지 않는 것이다. 이는 특히 임베디드 소프트웨어에서 중요하다.

volatile이 필요한 이유

0번지의 센서값을 받아와서 저장하는 변수 ADDR이 있다. 그리고 센서값을 10번 출력하고 있다고 하자.

#define ADDR (*(unsigned char *)0x00)
int main(){
    int sensor = 0;
    for(int i=0;i<10;i++){
        sensor = ADDR;
        printf("%d ", sensor);
    }
}

위의 코드에서 ADDR은 센서에 의해 값이 변경되지만 컴파일러는 센서의 존재를 모르기 때문에 ADDR은 영원히 0인 변수로 인식한다. 그리고 똑똑한 컴파일러라면 sensor 또한 값이 0에서 변경되지 않으므로 ADDR로부터 sensor 값을 굳이 받아오지 않고 0을 10번 프린트한다. 다시 말해 최적화를 위해서 메모리에서 매번 읽어오는게 아닌 캐싱된 데이터를 읽어온다.

이는 하드웨어에서 센서값을 받아오려고 했던 목적이 완전히 어긋난다. 이를 volatile로 해결할 수 있다.

#define ADDR (*(volatile unsigned char *)0x00)
int main(){
    int sensor = 0;
    for(int i=0;i<10;i++){
        sensor = ADDR;
        printf("%d ", sensor);
    }
}

위의 하드웨어 폴링은 임베디드 소프트웨어 영역이지만 순수 소프트웨어에서도 volatile이 필요할 수 있다. 하드웨어를 스레드로 바꾸어 생각하면 똑같은 일이 일어날 수 있다. 다른 스레드와 공유하고 있는 변수가 다른 스레드로 인해 값이 변경되지만 컴파일러가 이를 인식하지 못할 수 있기 때문이다. 그리고 처음에 언급한 내용이 없는 for문도 시간지연으로 쓸 수도 있다.

volatile 키워드는 사용 빈도가 극히 낮다. 아마 일반적인 기능 구현에는 사용할 일이 없을 것이다. 그러나 위와 같은 디버깅 상황에서 아주 진짜 혹시나 volatile이 필요할 수도 있다.

Leave a Comment