volatile type qualifier
C
타입 시스템
의 각 개별 타입에는 해당 타입의 여러
한정된(qualified)
버전이 존재하며, 이는
const
,
volatile
, 그리고 객체 타입에 대한 포인터의 경우
restrict
한정자 중 하나, 둘, 또는 모두 세 가지에 해당합니다. 이 페이지는
volatile
한정자의 효과에 대해 설명합니다.
최적화 목적을 위해 volatile 한정 타입의 lvalue 표현식을 통해 이루어지는 모든 접근(읽기 및 쓰기)은 관찰 가능한 부작용으로 간주되며, 추상 머신의 규칙에 따라 엄격하게 평가됩니다(즉, 모든 쓰기는 다음 시퀀스 포인트 전의 어떤 시점에 완료됩니다). 이는 단일 실행 스레드 내에서 volatile 접근이 최적화되어 제거되거나 volatile 접근과 sequence point 로 구분된 다른 가시적 부작용과 상대적으로 재정렬될 수 없음을 의미합니다.
비휘발성 값을 휘발성 타입으로 캐스팅하는 것은 아무런 효과가 없습니다. 비휘발성 객체를 휘발성 의미론을 사용하여 접근하려면, 해당 객체의 주소를 휘발성 포인터로 캐스팅한 후 그 포인터를 통해 접근해야 합니다.
volatile로 한정된 타입의 객체를 비-volatile 좌측값을 통해 읽거나 쓰려는 모든 시도는 정의되지 않은 동작을 초래합니다:
volatile int n = 1; // volatile 한정 타입의 객체 int* p = (int*)&n; int val = *p; // 정의되지 않은 동작
volatile로 한정된 구조체 또는 공용체 타입의 멤버는 해당 타입이 속한 한정을 획득합니다(
.
연산자나
->
연산자를 사용하여 접근할 때 모두):
struct s { int i; const int ci; } s; // s.i의 타입은 int, s.ci의 타입은 const int volatile struct s vs; // vs.i와 vs.ci의 타입은 volatile int와 const volatile int
|
배열 타입이 volatile 타입 한정자로 선언되면(
|
(C23 이전) |
|
배열 타입과 그 요소 타입은 항상 동일하게 volatile로 한정된 것으로 간주됩니다. |
(C23 이후) |
typedef int A[2][3]; volatile A a = {{4, 5, 6}, {7, 8, 9}}; // volatile int의 배열의 배열 int* pi = a[0]; // 오류: a[0]의 타입은 volatile int* void *unqual_ptr = a; // C23까지는 OK; C23 이후부터는 오류 // 참고: clang은 C89-C17 모드에서도 C++/C23 규칙을 적용함
함수 타입이 volatile 타입 한정자로 선언된 경우(
typedef
사용을 통해), 그 동작은 정의되지 않습니다.
|
함수 선언에서
다음 두 선언은 동일한 함수를 선언합니다: void f(double x[volatile], const double y[volatile]); void f(double * volatile x, const double * volatile y); |
(C99부터) |
비휘발성 타입에 대한 포인터는 동일하거나 호환 가능한 타입의 volatile 한정 버전에 대한 포인터로 암시적으로 변환될 수 있습니다. 역변환은 캐스트 표현식이 필요합니다.
int* p = 0; volatile int* vp = p; // OK: 한정자 추가 (int에서 volatile int로) p = vp; // Error: 한정자 제거 (volatile int에서 int로) p = (int*)vp; // OK: 캐스트
T
에 대한 포인터의 포인터는
volatile T
에 대한 포인터의 포인터로 변환할 수 없습니다. 두 타입이 호환되려면 해당 한정자(qualifications)가 동일해야 합니다:
char *p = 0; volatile char **vpp = &p; // 오류: char*와 volatile char*는 호환되지 않는 타입입니다 char * volatile *pvp = &p; // 정상, 한정자 추가 (char*에서 char*volatile로)
목차 |
volatile의 사용
static
volatile
객체는 메모리 매핑된 I/O 포트를 모델링하고,
static
const
volatile
객체는 실시간 클록과 같은 메모리 매핑된 입력 포트를 모델링합니다:
volatile short *ttyport = (volatile short*)TTYPORT_ADDR; for(int i = 0; i < N; ++i) *ttyport = a[i]; // *ttyport is an lvalue of type volatile short
volatile
변수 중에서
setjmp
매크로를 호출하는 함수 내에 지역적으로 선언된 변수들은
longjmp
가 반환된 후에도 그 값들을 유지하는 것이 보장되는 유일한 지역 변수들입니다.
volatile 변수는 스레드 간 통신에 적합하지 않습니다; 이들은 원자성, 동기화 또는 메모리 순서를 보장하지 않습니다. 동기화 없이 다른 스레드에 의해 수정되는 volatile 변수에 대한 읽기 또는 두 개의 비동기화 스레드로부터의 동시 수정은 데이터 경쟁으로 인해 정의되지 않은 동작입니다.
키워드
예제
최적화를 비활성화하기 위해 volatile 사용을 보여줍니다
#include <stdio.h> #include <time.h> int main(void) { clock_t t = clock(); double d = 0.0; for (int n = 0; n < 10000; ++n) for (int m = 0; m < 10000; ++m) d += d * n * m; // reads from and writes to a non-volatile printf("Modified a non-volatile variable 100m times. " "Time used: %.2f seconds\n", (double)(clock() - t)/CLOCKS_PER_SEC); t = clock(); volatile double vd = 0.0; for (int n = 0; n < 10000; ++n) for (int m = 0; m < 10000; ++m) { double prod = vd * n * m; // reads from a volatile vd += prod; // reads from and writes to a volatile } printf("Modified a volatile variable 100m times. " "Time used: %.2f seconds\n", (double)(clock() - t)/CLOCKS_PER_SEC); }
가능한 출력:
Modified a non-volatile variable 100m times. Time used: 0.00 seconds Modified a volatile variable 100m times. Time used: 0.79 seconds
참고문헌
- C17 표준 (ISO/IEC 9899:2018):
-
- 6.7.3 타입 한정자 (p: 87-90)
- C11 표준 (ISO/IEC 9899:2011):
-
- 6.7.3 타입 한정자 (p: 121-123)
- C99 표준 (ISO/IEC 9899:1999):
-
- 6.7.3 타입 한정자 (p: 108-110)
- C89/C90 표준 (ISO/IEC 9899:1990):
-
- 6.5.3 타입 한정자
참고 항목
|
C++ documentation
for
cv (
const
and
volatile
) type qualifiers
|