C 언어에서 volatile 키워드는 특정 변수에 대해 컴파일러가 최적화하지 않도록 지시하기 위해 사용된다. 컴파일러는 프로그램 실행 속도를 높이기 위해 코드와 변수의 최적화를 시도하는데, 이는 일반적인 상황에서는 유리할 수 있지만, 시스템 프로그래밍이나 하드웨어 관련 프로그래밍에서는 문제가 될 수 있다. volatile 키워드는 이러한 상황에서 변수의 값을 메모리에서 직접 가져오도록 보장한다.

1. 기본 개념

volatile 키워드를 붙인 변수는 메모리에 있는 값을 컴파일러가 캐시하지 않고 항상 메모리에서 읽어오도록 보장된다. 예를 들어 하드웨어 레지스터와 같은 값은 프로그램 외부 요인에 의해 변할 수 있기 때문에, 컴파일러는 해당 값을 캐시하지 않고 항상 실시간으로 읽어오게 해야 한다.

컴파일러는 최적화 과정에서 프로그램에서 사용되지 않는다고 판단한 변수에 대해 읽기와 쓰기 작업을 생략할 수 있다. 그러나 volatile 키워드가 붙은 변수는 이 최적화의 영향을 받지 않는다. 따라서 volatile을 사용하면 메모리 상의 값을 항상 읽어와야 하므로 변수 값이 프로그램 외부에서 변할 가능성을 고려하는 경우 유용하다.

2. volatile의 사용 목적

  1. 하드웨어 레지스터: 하드웨어 제어와 관련된 레지스터는 프로그램 외부의 요인에 의해 변경될 수 있다. 이 경우 레지스터의 값을 항상 메모리에서 읽어와야 정확한 값을 참조할 수 있다.

  2. 멀티스레드 프로그래밍: 다중 스레드가 변수에 접근할 때, 한 스레드가 값을 변경하더라도 다른 스레드는 이를 알지 못할 수 있다. volatile 키워드는 다른 스레드가 해당 변수의 최신 값을 볼 수 있게 해준다.

  3. 신호 처리기와의 상호작용: 신호 처리기는 프로그램의 주 실행 흐름과 독립적으로 변수를 변경할 수 있다. volatile 키워드를 통해 이러한 변수들이 올바르게 참조될 수 있도록 한다.

3. 예제

아래 예제에서는 volatile 키워드가 변수 flag에 사용된 경우와 사용되지 않은 경우를 비교하여 설명한다.

예를 들어 다음과 같은 코드가 있을 때:


int flag = 1;

while(flag) {
   // 작업 수행
}

컴파일러는 flag의 값이 변경되지 않으므로, 루프를 최적화하여 무한 루프를 제거할 수 있다. 그러나 만약 flag 변수가 외부 요인에 의해 변경될 가능성이 있다면, 최적화는 문제가 될 수 있다.

이러한 경우 volatile 키워드를 사용하여 다음과 같이 선언한다:

volatile int flag = 1;

이제 컴파일러는 flag의 값을 항상 메모리에서 읽어와야 하므로 외부 요인에 의해 변경된 값을 반영할 수 있다.

4. volatile과 멀티스레드

volatile은 멀티스레드 환경에서 변수 값의 최신성을 보장하는데 유용하지만, 동기화를 제공하지 않는다. 이는 변수의 읽기와 쓰기가 원자적으로 이루어지는 것을 보장하지 않는다. 따라서 volatile 변수에 대한 동시 접근이 존재하는 멀티스레드 환경에서는 mutexatomic 연산과 같은 추가적인 동기화가 필요하다.

5. volatile과 메모리 장벽

volatile 키워드만으로는 컴파일러 최적화를 억제하지만, CPU 재정렬 방지까지 보장하지는 않는다. 메모리 장벽(memory barrier)과 함께 사용하는 것이 일반적이다. 메모리 장벽은 컴파일러와 프로세서가 특정 코드의 순서를 변경하지 않도록 방지하여 더 정밀한 제어를 가능하게 한다.

결론

C 언어의 volatile 키워드는 변수의 값을 항상 메모리에서 읽도록 하여 예측 불가능한 외부 요인에 의해 변경될 수 있는 상황에서 매우 유용하다. 그러나 volatile만으로 멀티스레드 환경의 동기화 문제를 해결할 수는 없으며, 필요시 추가적인 동기화 기법을 사용해야 한다.