Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

C 언어의 volatile과 restrict 타입 한정자 쉽게 이해하기

C 프로그래밍 언어에서 타입 한정자(type qualifier)는 기존 데이터 타입에 특별한 속성을 부여하는 키워드입니다. C 언어에는 const, volatile, restrict 세 가지 타입 한정자가 있으며, 이 글에서는 그중 volatilerestrict에 대해 자세히 알아보겠습니다.

volatile 한정자란?

volatile은 변수가 '공유(shared)'되고 있음을 컴파일러에게 알려주는 역할을 합니다. 즉, 어떤 변수를 volatile로 선언하면 그 변수는 프로그램 외부의 다른 개체(하드웨어 레지스터, 인터럽트 서비스 루틴, 멀티스레드 환경의 다른 스레드 등)에 의해 언제든지 참조되거나 변경될 수 있습니다.

따라서 컴파일러는 이러한 변수에 대해 불필요한 최적화를 수행하지 않고, 매번 메모리에서 실제 값을 새로 읽어오도록 코드를 생성합니다.

선언 예시:

volatile int x;

restrict 한정자란?

restrict는 포인터에만 사용할 수 있는 한정자로, C99 표준에서 도입되었습니다. 이 한정자가 붙은 포인터는 해당 포인터가 가리키는 데이터에 접근할 수 있는 사실상 유일한 경로임을 의미합니다. 이러한 보장이 있으면 컴파일러는 더 공격적인 최적화를 수행할 수 있습니다.

restrict가 필요한 이유

먼저 일반 포인터를 사용한 경우를 살펴보겠습니다.

int *ptr;
int a = 0;
ptr = &a;
____
____
____
    *ptr += 4; // *ptr += 9로 합칠 수 없음
____
____
____
    *ptr += 5;

위 코드에서 컴파일러는 *ptr += 4;*ptr += 5; 두 문장을 *ptr += 9; 하나로 합칠 수 없습니다. 그 이유는 두 문장 사이에서 변수 a가 직접적으로 또는 다른 포인터를 통해 변경되었을 가능성을 배제할 수 없기 때문입니다.

restrict 적용 시

int *restrict ptr;
int a = 0;
ptr = &a;
____
____
____
    *ptr += 4; // *ptr += 9로 합칠 수 있음
____
____
    *ptr += 5;
____
____

반면 restrict를 사용하면 컴파일러는 두 문장을 *ptr += 9; 하나로 안전하게 합칠 수 있습니다. 해당 포인터 외에는 변수에 접근할 수 있는 다른 경로가 없다는 것이 보장되기 때문입니다.

restrict 활용 예제

다음은 restrict 키워드를 사용한 C 프로그램입니다.

#include <stdio.h>

void keyword(int* a, int* b, int* restrict c) {
    *a += *c;
    // c가 restrict이므로 컴파일러는
    // 어셈블리 코드에서 c 주소의 값을
    // 다시 로드하지 않습니다.
    *b += *c;
}

int main(void) {
    int p = 10, q = 20, r = 30;
    keyword(&p, &q, &r);
    printf("%d %d %d", p, q, r);
    return 0;
}

실행 결과

위 프로그램을 실행하면 다음과 같은 결과가 출력됩니다.

40 50 30

함수 keyword에서 crestrict 포인터이므로, 컴파일러는 *a += *c; 실행 후 c가 가리키는 값을 다시 메모리에서 읽어오지 않아도 된다고 판단합니다. 이를 통해 불필요한 메모리 접근을 줄여 프로그램의 성능을 향상시킬 수 있습니다.