Volatile 변수
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
volatile은 C, C++, Java, C#, Fortran과 같은 프로그래밍 언어에서 사용되는 키워드로, 주로 컴파일러의 최적화를 제어하고, 멀티스레딩 환경에서 변수의 가시성을 보장하며, 메모리 맵 I/O와 같은 특수한 상황에서 변수의 값을 정확하게 유지하는 데 사용된다. C와 C++에서는 메모리 맵 I/O, `setjmp`/`longjmp`를 통한 값 유지, 시그널 핸들러와 프로그램 간의 값 공유를 위해 사용되며, 컴파일러가 변수를 최적화하는 것을 방지하여 하드웨어 값을 폴링하는 데 활용된다. Java에서는 원자적 연산, 단일 전역 순서, 획득/해제 메모리 장벽을 통해 멀티스레딩 환경에서 안전한 코드 작성을 지원한다. C#에서는 스레드 안전하지 않은 최적화를 방지하며, `Thread.VolatileRead` 및 `Thread.VolatileWrite` 메서드를 통해 더 강력한 메모리 가시성 보장을 제공한다. Fortran에서는 변수의 값을 메모리에서 직접 읽고 쓰도록 하여 최적화를 억제하고, 에일리어싱 관련 버그를 찾는 데 도움을 준다.
더 읽어볼만한 페이지
- 변수 (컴퓨터 과학) - 멤버 변수
멤버 변수는 객체 지향 프로그래밍에서 객체의 속성을 저장하고 관리하며 객체의 상태를 나타내는 변수로, 지역 변수와 달리 객체의 생명 주기와 함께 값을 유지한다. - 변수 (컴퓨터 과학) - 정적 변수
정적 변수는 프로그램 실행 시간 동안 값을 유지하며, C 언어에서 `static` 키워드로 정의되어 함수 호출 간에 값을 유지하고, 객체 지향 프로그래밍에서 클래스의 모든 인스턴스에서 공유되는 클래스 변수로 사용된다. - 동시성 제어 - 세마포어
세마포어는 데이크스트라가 고안한 정수 변수로, P/V 연산을 통해 자원 접근을 제어하고 동기화 문제를 해결하며, 계수 세마포어와 이진 세마포어로 나뉘어 멀티스레드 환경에서 자원 관리 및 스레드 동기화에 기여한다. - 동시성 제어 - 모니터 (동기화)
모니터는 공유 자원 접근을 제어하여 프로세스 간 동기화를 구현하는 프로그래밍 구조로, 뮤텍스 락, 조건 변수 등으로 구성되어 경쟁 상태를 방지하며 여러 프로그래밍 언어에서 지원된다. - C 프로그래밍 언어 - C (프로그래밍 언어)
C는 하드웨어 제어와 이식성이 뛰어난 고급 절차적 프로그래밍 언어로서, 다양한 분야에서 사용되며 후속 언어에 영향을 주었고, 성능과 효율성이 높지만 안전성 문제 개선이 필요한 언어이다. - C 프로그래밍 언어 - 헤더 파일
헤더 파일은 프로그래밍 언어에서 코드 재사용성, 모듈화, 컴파일 시간 단축에 기여하며 함수 프로토타입, 변수 선언 등을 포함하고 `#include` 지시어로 소스 코드에 포함되어 사용되는 파일이다.
Volatile 변수 | |
---|---|
휘발성 (컴퓨터 프로그래밍) | |
종류 | 키워드 |
사용되는 언어 | C C++ C# Java |
설명 | 변수가 컴파일러에 의해 최적화되지 않도록 지정하는 데 사용되는 키워드 |
세부 정보 | |
최적화 방지 | 휘발성 변수는 컴파일러가 변수를 최적화하지 않도록 강제함 |
외부 영향 고려 | 변수의 값이 외부 요인에 의해 변경될 수 있음을 컴파일러에 알림 |
사용 예시 | 하드웨어 레지스터 인터럽트 핸들러 공유 메모리 |
2. C/C++ 에서의 Volatile
C와 C++에서 ''volatile''은 `const`와 같은 형용사이며, 자료형의 일부로 취급된다. 주로 메모리 맵 입출력(MMIO)을 제어하거나, 특정 상황에서 컴파일러가 코드의 의미를 변경할 수 있는 최적화를 수행하지 못하도록 막는 역할을 한다.
때때로 ''volatile'' 키워드의 동작은 컴파일러가 기존의 ''volatile'' 변수에 대한 읽기 및 쓰기 작업을 제거하거나, 새로운 읽기/쓰기 작업을 추가하거나, 작업 순서를 변경하는 최적화를 억제하는 것으로 설명되기도 한다. 그러나 이는 기본적인 이해를 돕기 위한 설명이며, 실제 프로덕션 코드를 작성할 때는 이 설명에만 의존해서는 안 된다.
C와 C++ 표준에서는 ''volatile'' 키워드를 다음과 같은 목적으로 사용하도록 의도했다:[1]
- 메모리 맵 I/O 장치에 접근하기 위함.
- `setjmp`와 `longjmp` 함수 호출 사이에서 특정 변수의 값이 유지되도록 보장하기 위함.
- 시그널 핸들러와 프로그램의 나머지 부분 간에 ''volatile sig_atomic_t'' 타입의 객체를 통해 값을 안전하게 공유하기 위함.
표준에 따르면 `setjmp`/`longjmp`나 시그널 핸들러와의 값 공유를 위해 ''volatile''을 사용하는 것은 이식 가능한 코드를 작성하는 데 도움이 될 수 있다. 하지만 메모리 맵 I/O 장치 접근을 위해 ''volatile''을 사용하는 코드는 본질적으로 특정 하드웨어나 구현에 의존적이므로 이식성이 낮으며, 해당 시스템에 대한 깊은 이해가 필요하다.
2. 1. C/C++ 에서 volatile의 역할
주로 메모리 맵 입출력(MMIO)을 제어할 때, ''volatile''로 선언된 변수는 컴파일러의 최적화를 방지하는 역할을 한다.다음은 ''volatile''이 없을 때 발생할 수 있는 최적화 예시이다.
static int foo;
void bar(void)
{
foo = 0;
while (foo != 255); // foo 값이 루프 내에서 변하지 않으므로 컴파일러는 무한 루프로 판단할 수 있다.
}
위 코드에서 변수 ''foo''는 초기값이 0으로 설정된 후, while 루프 안에서 값이 변경되지 않는다. 따라서 컴파일러는 `while (foo != 255)` 조건이 항상 true라고 판단하여 코드를 다음과 같이 최적화할 수 있다.
void bar_optimized(void)
{
foo = 0;
while (true); // 무한 루프
}
이렇게 되면 프로그램은 의도치 않은 무한 루프에 빠지게 된다. 이러한 최적화를 방지하기 위해 ''volatile'' 키워드를 사용한다.
static volatile int foo; // volatile 키워드 추가
void bar (void)
{
foo = 0;
while (foo != 255); // 컴파일러는 foo 값이 외부 요인에 의해 변경될 수 있다고 간주하여 최적화하지 않는다.
}
''volatile'' 키워드를 사용하면 컴파일러는 해당 변수의 값이 언제든지 외부 요인(예: 하드웨어, 다른 스레드)에 의해 변경될 수 있다고 가정한다. 따라서 컴파일러는 변수 접근과 관련된 최적화를 수행하지 않고, 개발자가 작성한 코드 그대로 기계어 코드를 생성한다. 때때로 ''volatile''의 동작은 컴파일러 최적화를 억제하는 것으로 설명되기도 한다: 기존의 ''volatile'' 읽기/쓰기를 제거하지 않고, 새로운 ''volatile'' 읽기/쓰기를 추가하지 않으며, ''volatile'' 읽기/쓰기의 순서를 변경하지 않는다는 것이다. 그러나 이는 단순화된 설명이며 실제 코드 작성 시에는 주의가 필요하다. 위 예시 코드만 보면 여전히 무한 루프처럼 보일 수 있지만, 만약 ''foo''가 특정 하드웨어 장치의 레지스터를 가리킨다면, 하드웨어 상태 변화에 따라 ''foo''의 값이 변경될 수 있다. 이런 방식으로 ''volatile'' 변수는 하드웨어의 상태를 주기적으로 확인하는 폴링(polling) 방식에 사용될 수 있다.
C와 C++에서 ''volatile''은 `const`와 마찬가지로 형용사 역할을 하며, 변수나 필드의 자료형 일부로 취급된다.
C와 C++ 표준에 따르면 ''volatile'' 키워드는 주로 다음 세 가지 목적으로 사용된다:[1]
- 메모리 맵 I/O(MMIO) 장치에 접근하기 위해 사용된다.
- setjmp와 longjmp 함수 호출 사이에서 변수 값을 유지하기 위해 사용된다.
- 시그널 핸들러와 프로그램의 나머지 부분 간에 ''volatile sig_atomic_t'' 타입의 객체를 통해 값을 안전하게 공유하기 위해 사용된다.
C 및 C++ 표준은 ''volatile'' 객체를 사용하여 `setjmp`/`longjmp`를 통해 값을 공유하거나, ''volatile sig_atomic_t'' 객체를 사용하여 시그널 핸들러와 나머지 코드 간에 값을 공유하는 이식 가능한 코드를 작성할 수 있도록 허용한다. 그러나 메모리 맵 I/O 장치 접근을 위해 ''volatile'' 키워드를 사용하는 것은 본질적으로 이식성이 없으며, 특정 대상 C/C++ 구현 및 플랫폼에 대한 깊은 이해가 필요하다.
2. 2. 멀티스레딩 환경에서의 Volatile
`volatile
` 키워드가 C 및 C++에서 이식 가능한 멀티스레딩 코드에 유용하다는 것은 흔한 오해이다. C 및 C++의 `volatile
` 키워드는 어떤 멀티스레딩 시나리오에도 유용한 이식 가능한 도구로 작동하지 않았다.[2][3][4][5] 자바 및 C# 프로그래밍 언어와 달리, C 및 C++의 `volatile
` 변수에 대한 연산은 원자적이지 않으며, `volatile
` 변수에 대한 연산은 충분한 메모리 순서 보장(즉, 메모리 장벽)을 갖지 않는다. 대부분의 C 및 C++ 컴파일러, 링커 및 런타임은 `volatile
` 키워드를 어떤 멀티스레딩 시나리오에도 유용하게 만들 수 있는 필요한 메모리 순서 보장을 제공하지 않는다. C11 및 C++11 표준 이전에는 프로그래머가 멀티스레딩 코드를 작성하기 위해 개별 구현 및 플랫폼(예: POSIX 및 WIN32)의 보장에 의존해야 했다. 최신 C11 및 C++11 표준을 사용하면 프로그래머는 std::atomic
템플릿과 같은 새로운 이식 가능한 구문을 사용하여 이식 가능한 멀티스레딩 코드를 작성할 수 있다.[6]2. 3. C언어 MMIO에서의 적용 예시
주로 메모리 맵 입출력(MMIO)을 제어할 때, `volatile`로 선언된 변수를 사용하여 컴파일러의 최적화를 방지하는 역할을 한다.다음은 `volatile` 키워드가 없을 때 발생할 수 있는 문제 상황을 보여주는 코드 예시이다.
static int foo;
void bar(void)
{
foo = 0;
while (foo != 255); // foo 값이 255가 될 때까지 기다림
}
이 코드에서 `foo`의 초기값은 0이고, while 루프 안에서는 `foo`의 값을 변경하는 코드가 없다. 따라서 컴파일러는 `foo`의 값이 항상 0일 것이라고 판단하여 코드를 최적화할 수 있다. 예를 들어, 다음과 같이 무한 루프로 변경할 수 있다.
void bar_optimized(void)
{
foo = 0;
while (true); // 무한 루프
}
만약 `foo`가 CPU에 연결된 장치의 하드웨어 레지스터처럼 프로그램의 실행 흐름과 독립적으로 값이 변경될 수 있는 경우, 이러한 최적화는 심각한 문제를 일으킨다. 컴파일러는 다른 코드가 `foo` 값을 변경할 수 없다고 가정하고 루프 불변 코드 이동과 같은 최적화를 수행하여 `while (foo != 255)` 검사를 루프 밖으로 빼내거나 상수 값(`true`)으로 대체할 수 있다. 이렇게 되면 프로그램은 `foo` 값의 실제 변경을 감지하지 못하고 영원히 루프에 갇히게 된다.
이러한 컴파일러의 최적화를 방지하고, 변수 값이 언제든지 외부 요인에 의해 변경될 수 있음을 알리기 위해 `volatile` 키워드를 사용한다.
static volatile int foo; // volatile 키워드 추가
void bar (void)
{
foo = 0;
while (foo != 255); // foo 값이 255가 될 때까지 매번 값을 다시 읽음
}
`volatile` 키워드를 사용하면 컴파일러는 `foo` 변수에 대한 접근(읽기 또는 쓰기)을 최적화하지 않고, 코드에 명시된 대로 매번 메모리나 레지스터에 접근하는 기계어 코드를 생성한다. 따라서 위 예시의 `while` 루프는 매번 `foo`의 현재 값을 읽어와서 255와 비교하게 된다.
이러한 특성 때문에 `volatile` 변수는 하드웨어 레지스터의 상태를 주기적으로 확인하는 폴링 방식이나, 인터럽트 서비스 루틴과 일반 코드 간의 데이터 공유 등에서 유용하게 사용된다. 즉, 컴파일러에게 변수 값의 변화를 예측하지 말고 항상 실제 값을 참조하도록 강제하는 역할을 한다.
2. 4. 최적화 비교
다음 C 프로그램과 그에 대응하는 어셈블리어 발췌문은 `volatile` 키워드가 컴파일러의 출력에 어떤 영향을 미치는지 보여준다. 이 예시에서는 GCC를 사용하였다.어셈블리 코드를 비교하면, `volatile` 키워드를 사용하여 생성된 코드가 더 길고 자세하다는 것을 명확히 알 수 있다. 이는 `volatile` 객체의 특성을 만족시키기 위함이다. `volatile` 키워드는 컴파일러가 해당 변수를 포함하는 코드에 대해 최적화를 수행하는 것을 방지한다. 결과적으로, `volatile` 변수에 대한 모든 할당과 읽기는 실제 메모리 접근으로 이어진다. 반면, `volatile` 키워드가 없으면 컴파일러는 다른 스레드나 프로세스에서 해당 메모리 위치를 수정하지 않는다고 가정하고, 변수를 사용할 때마다 메모리에서 다시 읽어올 필요 없이 최적화를 수행할 수 있다.
어셈블리 비교 | |
---|---|
volatile 키워드 없이 | volatile 키워드와 함께 |
gcc -S -O3 -masm=intel noVolatileVar.c -o without.s | gcc -S -O3 -masm=intel VolatileVar.c -o with.s |
모든 최신 버전의 자바 프로그래밍 언어에서 ''volatile'' 키워드는 다음과 같은 보장을 제공한다.
3. Java 에서의 Volatile
이러한 보장 덕분에 ''volatile''은 자바에서 유용한 멀티스레딩 구성 요소로 사용된다. 특히, ''volatile''과 함께 사용되는 일반적인 이중 확인 잠금 패턴은 자바에서 올바르게 작동한다.[12]
Java 5 이전 버전에서는 Java 표준이 ''volatile'' 읽기/쓰기와 비-''volatile'' 읽기/쓰기 사이의 상대적 순서를 보장하지 않았다. 즉, ''volatile''은 "획득(acquire)" 및 "해제(release)" 메모리 장벽 의미를 갖지 않았다. 이로 인해 멀티스레딩 구성 요소로서 ''volatile''의 활용이 크게 제한되었으며, 특히 ''volatile''을 사용한 일반적인 이중 확인 잠금 패턴은 제대로 작동하지 않았다.
4. C# 에서의 Volatile
C#에서 ''volatile'' 키워드는 특정 필드에 접근하는 코드가 컴파일러, CLR, 또는 하드웨어 수준에서 발생할 수 있는 일부 스레드 안전하지 않은 최적화의 영향을 받지 않도록 보장한다. 필드가 ''volatile''로 표시되면, 컴파일러는 해당 필드와 관련된 명령어 재정렬이나 캐싱을 방지하는 "메모리 장벽" 또는 "펜스(fence)"를 생성하도록 지시받는다.
구체적으로, ''volatile'' 필드를 읽을 때는 컴파일러가 획득 펜스(acquire fence)를 생성한다. 이 펜스는 다른 필드의 읽기 및 쓰기 작업이 이 펜스 ''앞으로'' 이동하는 것을 막는다. 반대로, ''volatile'' 필드에 쓸 때는 컴파일러가 해제 펜스(release fence)를 생성하며, 이 펜스는 다른 필드의 읽기 및 쓰기 작업이 이 펜스 ''뒤로'' 이동하는 것을 방지한다.[13]
모든 자료형을 ''volatile''로 선언할 수 있는 것은 아니다. 다음은 ''volatile''로 표시할 수 있는 자료형 목록이다.
구분 | 자료형 |
---|---|
참조 형식 | 모든 참조 형식 |
기본 형식 | Single, Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Char |
열거형 | 기본 형식이 Byte, SByte, Int16, UInt16, Int32, UInt32인 모든 열거형 |
- '''주의:''' 값 구조체나 기본 형식 중 Double, Int64, UInt64, Decimal 등은 ''volatile''로 선언할 수 없다.
''volatile'' 키워드는 변수가 참조로 전달되거나 캡처된 지역 변수인 경우에는 사용할 수 없다. 이러한 상황에서는 ''Thread.VolatileRead'' 및 ''Thread.VolatileWrite'' 메서드를 대신 사용해야 한다.[13]
이 메서드들은 C# 컴파일러, JIT 컴파일러, 또는 CPU 자체에서 일반적으로 수행되는 일부 최적화 작업을 비활성화하는 효과를 가진다. ''Thread.VolatileRead'' 및 ''Thread.VolatileWrite''가 제공하는 보장은 ''volatile'' 키워드가 제공하는 것보다 더 강력하다. ''volatile'' 키워드가 생성하는 "반쪽 펜스"(획득 펜스는 앞에 오는 재정렬 및 캐싱만 방지)와 달리, ''VolatileRead'' 및 ''VolatileWrite''는 "전체 펜스(full fence)"를 생성하여 양방향으로 해당 필드의 명령어 재정렬 및 캐싱을 막는다.[13] 이 메서드들의 동작 방식은 다음과 같다.[15]
- ''Thread.VolatileWrite'': 메서드 호출 시점에 필드의 값이 즉시 기록되도록 강제한다. 또한, 이 호출 이전에 프로그램 순서상 존재했던 로드 및 저장 작업은 반드시 ''VolatileWrite'' 호출 전에 발생해야 하며, 호출 이후의 로드 및 저장 작업은 반드시 호출 후에 발생해야 한다.
- ''Thread.VolatileRead'': 메서드 호출 시점에 필드의 값을 즉시 읽도록 강제한다. 마찬가지로, 이 호출 이전에 프로그램 순서상 존재했던 로드 및 저장 작업은 반드시 ''VolatileRead'' 호출 전에 발생해야 하며, 호출 이후의 로드 및 저장 작업은 반드시 호출 후에 발생해야 한다.
''Thread.VolatileRead''와 ''Thread.VolatileWrite''는 내부적으로 ''Thread.MemoryBarrier'' 메서드를 호출하여 양방향으로 작동하는 메모리 장벽, 즉 전체 펜스를 생성한다. ''volatile'' 키워드의 반쪽 펜스는 비대칭적이기 때문에, 읽기 명령 다음에 쓰기 명령이 오는 경우 컴파일러가 실행 순서를 바꿀 수 있는 잠재적 문제가 있다. 하지만 ''Thread.MemoryBarrier''를 통해 생성된 전체 펜스는 대칭적이므로 이러한 문제가 발생하지 않는다.[13]
5. Fortran 에서의 Volatile
VOLATILE
은 포트란 포트란 2003 표준의 일부이며,[16] 이전 버전에서도 확장 기능으로 지원되었다. 함수 내의 모든 변수를 volatile
로 지정하는 것은 에일리어싱 관련 버그를 찾는 데에도 유용하다.
integer, volatile :: i ! volatile로 정의되지 않은 경우 다음 두 줄의 코드는 동일하다.
write(*,*) i**2 ! 변수 i를 메모리에서 한 번 로드하여 해당 값을 자체에 곱한다.
write(*,*) i*i ! 변수 i를 메모리에서 두 번 로드하여 해당 값을 곱한다.VOLATILE
변수는 메모리에 항상 접근하기 때문에, 포트란 컴파일러는 volatile 변수에 대한 읽기 또는 쓰기의 순서를 변경할 수 없다. 이는 다른 스레드에서 현재 스레드의 작업 결과를 볼 수 있게 하며, 그 반대의 경우도 마찬가지이다.[17]VOLATILE
을 사용하면 최적화가 감소하거나 심지어 방지될 수도 있다.[18]
참조
[1]
웹사이트
Publication on C++ standards committee
http://www.open-std.[...]
[2]
웹사이트
Volatile Keyword In Visual C++
http://msdn2.microso[...]
2021-09-21
[3]
웹사이트
Linux Kernel Documentation – Why the "volatile" type class should not be used
https://www.kernel.o[...]
[4]
웹사이트
C++ and the Perils of Double-Checked Locking
http://www.aristeia.[...]
[5]
웹사이트
Linux: Volatile Superstition
http://kerneltrap.or[...]
kerneltrap.org
2011-01-09
[6]
웹사이트
volatile (C++)
https://msdn.microso[...]
2021-09-21
[7]
문서
"Clarification Request Summary for C11."
http://www.open-std.[...]
[8]
간행물
Volatiles Are Miscompiled, and What to Do about It
https://users.cs.uta[...]
2008-10
[9]
웹사이트
Volatile Bugs, Three Years Later – Embedded in Academia
https://blog.regehr.[...]
2024-08-28
[10]
웹사이트
The Java® Language Specification, Java SE 7 Edition
http://docs.oracle.c[...]
Oracle Corporation
2013-05-12
[11]
웹사이트
Java Concurrency: Understanding the 'Volatile' Keyword
https://dzone.com/ar[...]
dzone.com
2021-03-08
[12]
웹사이트
Double-checked Locking (DCL) and how to fix it
http://www.javamex.c[...]
Javamex
2009-09-19
[13]
웹사이트
Part 4: Advanced Threading
http://www.albahari.[...]
O'Reilly Media
2019-12-09
[14]
서적
CLR Via C#
https://archive.org/[...]
Microsoft Press
2010-02-11
[15]
서적
CLR Via C#
https://archive.org/[...]
Microsoft Press
2010-02-11
[16]
웹사이트
VOLATILE Attribute and Statement
https://web.archive.[...]
Cray
2016-04-22
[17]
웹사이트
Volatile and shared array in Fortran
https://software.int[...]
[18]
웹사이트
VOLATILE
https://docs.oracle.[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com