volatile
volatile
Zig는 평범한 로드(load)와 스토어(store)가 부수 효과(side effect)를 갖지 않는다고 가정해요. 그런데 실제 하드웨어를 다루다 보면 이 가정이 성립하지 않는 경우가 생기죠. 대표적인 예가 MMIO(Memory Mapped Input/Output) 인데요, 특정 메모리 주소를 읽거나 쓰는 그 동작 자체가 의미를 갖는 경우예요. 이런 상황에서 로드·스토어가 실제로 발생하도록 보장하고 싶다면 volatile 키워드를 붙이면 됩니다.
본문
volatile을 붙인 포인터에 대한 로드와 스토어는 모두 실제로 일어나는 게 보장되고, 소스 코드에 적힌 순서 그대로 실행돼요. 다음 코드에서 mmio_ptr을 통한 로드·스토어는 항상 발생하고 순서도 유지된다고 보장됩니다.
const expectEqual = @import("std").testing.expectEqual;
test "volatile" {
const mmio_ptr: *volatile u8 = @ptrFromInt(0x12345678);
try expectEqual(*volatile u8, @TypeOf(mmio_ptr));
}
$ zig test test_volatile.zig
1/1 test_volatile.test.volatile...OK
All 1 tests passed.
여기서 @ptrFromInt(0x12345678)로 하드웨어 레지스터 주소(가상의 MMIO 주소)를 가리키는 포인터를 만들고, 그 타입이 *volatile u8인지 확인하고 있어요. MMIO처럼 특정 주소에 읽기·쓰기 자체가 동작인 경우에는 이 보장이 매우 중요합니다.
한 가지 꼭 짚고 넘어갈 점이 있어요. volatile은 동시성(concurrency)이나 Atomics와는 아무 관련이 없습니다. 스레드 간 동기화를 위해 쓰는 것도 아니고, 그런 용도로도 쓸 수 없어요. 그래서 MMIO 외의 다른 목적으로 volatile을 쓰는 코드를 발견한다면, 십중팔구 버그로 봐야 해요.
더 알아보기
@ptrFromInt— 정수 값을 메모리 주소로 바꿔 포인터를 만드는 내장 함수Atomics— 동시성 문제를 다룰 때는volatile이 아니라 Atomics를 써요