Quick Reference
volatile과 restrict는 둘 다 최적화 주변에 있지만 해결하는 문제는 완전히 다릅니다.
| qualifier | 약속 | 쓰지 않는 문제 |
|---|---|---|
volatile | 해당 객체 접근을 관찰 가능하게 유지 | 원자성·thread synchronization |
restrict | 제한된 포인터 경로의 aliasing 없음 | 겹치는 buffer 복사 |
_Atomic 또는 lock | 동시 접근 동기화 | volatile로 대체하지 않음 |
volatile uint32_t *status = (volatile uint32_t *)0x40000000;
void copy_fast(int * restrict dst, const int * restrict src, int n);역할 구분
즉 하나는 "접근을 보존"하는 쪽이고, 다른 하나는 "더 공격적으로 최적화해도 된다"는 약속입니다.
문법
- 메모리 매핑 I/O
- 하드웨어 레지스터
- 시그널 핸들러와 공유하는 플래그
volatile uint32_t *status = (volatile uint32_t *)0x40000000;
while ((*status & 0x01u) == 0) {
}여기서 중요한 것은 값이 "코드 바깥"에서 바뀔 수 있다는 점입니다. volatile은 그래서 접근 자체를 보존하는 데 쓰이지만, 원자성이나 스레드 간 동기화를 대신해 주지는 않습니다.
시그널 핸들러와 공유하는 단순 플래그는 보통 volatile sig_atomic_t처럼 다루지만, 멀티스레드 동기화는 volatile로 해결되지 않습니다. 원자성이나 메모리 순서를 보장하지 않기 때문입니다.
volatile은 volatile 객체에 대한 접근을 관찰 가능하게 할 뿐, 여러 바이트 읽기·수정·쓰기의 원자성이나 장치 프로토콜을 보장하지 않습니다. 장치 레지스터는 보드 지원 패키지가 제공하는 타입과 접근 순서를 우선합니다.
즉 thread-safe counter 같은 문제는 _Atomic이나 락 영역입니다.
void copy_fast(int * restrict dst, const int * restrict src, int n) {
for (int i = 0; i < n; i++) {
dst[i] = src[i];
}
}이 선언은 컴파일러에게 dst와 src가 같은 메모리를 가리키지 않는다고 말하는 셈입니다. 그래서 더 공격적인 최적화를 시도할 수 있습니다.
restrict는 단순히 두 주소가 같지 않다는 주석이 아니라, 제한된 포인터를 통해 접근한 객체를 같은 실행 구간에서 충돌하는 다른 경로로 접근하지 않겠다는 계약입니다. 실제로 겹치는 버퍼에는 붙이지 말고 memmove를 선택합니다.
선택 기준
- 하드웨어/시그널 쪽 접근 보존:
volatile - 포인터 비겹침 약속:
restrict - 멀티스레드 동기화는
volatile아님 - 겹치는 복사는
restrict보다memmove쪽 - 실제로 겹치는 포인터에
restrict를 붙이면 안 된다
주의할 점
volatile과 restrict는 둘 다 "성능/최적화" 주변 키워드라 같이 묶여 보이지만, 실제로는 반대 방향의 제약을 줍니다. 의미를 정확히 모른 채 붙이면 성능도, 정확성도 둘 다 잃기 쉽습니다.
참고 링크
2 sources