const 타입 한정자

const 타입 한정자 (const type qualifier)

C의 타입 시스템에는 각 타입마다 const, volatile, 그리고 객체 타입을 가리키는 포인터에는 restrict라는 한정자(qualifier) 를 붙여 만든 여러 한정된 버전이 있어요. 이 페이지에서는 그중 const 한정자가 무슨 일을 하는지 자세히 볼게요.

출처: cppreference

본문

const 한정 타입으로 선언된 객체는 컴파일러가 읽기 전용 메모리에 둘 수 있어요. 그리고 프로그램에서 그 const 객체의 주소를 한 번도 취하지 않으면, 아예 저장하지 않을 수도 있어요.

const 한정 타입의 객체를 수정하려는 어떤 시도도 정의되지 않은 동작(undefined behavior) 이에요.

const int n = 1; // const 한정 타입의 객체
int* p = (int*)&n;
*p = 2; // 정의되지 않은 동작

const 의미론은 lvalue 표현식에만 적용돼요. lvalue가 필요 없는 문맥에서 const lvalue 표현식을 쓰면 그 const 한정자는 사라져요(참고: volatile 한정자는 있다면 사라지지 않아요).

const 한정 타입의 객체를 지정하는 lvalue 표현식과, const 한정 타입의 멤버를 하나 이상 가진 struct·union 타입(재귀적으로 포함된 집합체·공용체의 멤버도 포함)의 객체를 지정하는 lvalue 표현식은 수정 가능한 lvalue(modifiable lvalue)가 아니에요. 특히 대입할 수 없어요:

const int n = 1; // const 타입의 객체
n = 2; // 오류: n의 타입이 const 한정

int x = 2; // 한정되지 않은 타입의 객체
const int* p = &x;
*p = 3; // 오류: lvalue *p의 타입이 const 한정

struct {int a; const int b; } s1 = {.b=1}, s2 = {.b=2};
s1 = s2; // 오류: s1의 타입은 한정되지 않지만 const 멤버를 가짐

const 한정 구조체·공용체 타입의 멤버는 그것이 속한 타입의 한정을 물려받아요(. 연산자로 접근하든 -> 연산자로 접근하든 마찬가지예요).

struct s { int i; const int ci; } s;
// s.i의 타입은 int, s.ci의 타입은 const int
const struct s cs;
// cs.i와 cs.ci의 타입은 둘 다 const int
배열 타입을 const 타입 한정자로 선언하면(typedef를 통해) 배열 타입 자체가 const 한정되지는 않고, 그 요소 타입이 const 한정돼요. (until C23)
배열 타입과 그 요소 타입은 항상 동일하게 const 한정된 것으로 간주돼요. (since C23)
typedef int A[2][3];
const A a = {{4, 5, 6}, {7, 8, 9}}; // const int의 배열의 배열
int* pi = a[0]; // 오류: a[0]은 const int* 타입
void *unqual_ptr = a; // C23까지는 OK, C23부터는 오류
// 참고: clang은 C++/C23 규칙을 C89-C17 모드에서도 적용함

함수 타입을 const 타입 한정자로 선언하면(typedef를 통해) 동작은 정의되지 않아요.

함수 선언에서 키워드 const는 함수 매개변수의 배열 타입을 선언하는 대괄호 안에 나타날 수 있어요. 이때 배열 타입이 변환되는 포인터 타입을 한정해요. 다음 두 선언은 같은 함수를 선언해요: void f(double x[const], const double y[const]); void f(double *const x, const double *const y); (since C99)
const 한정 복합 리터럴은 반드시 서로 다른 객체를 지정하지는 않아요. 다른 복합 리터럴과, 우연히 같거나 겹치는 표현을 가진 문자열 리터럴과 저장 공간을 공유할 수 있어요. const int * p1 = ( const int []){ 1 , 2 , 3 }; const int * p2 = ( const int []){ 2 , 3 , 4 }; // p2의 값은 p1+1과 같을 수 있음 _Bool b = "foobar" + 3 == ( const char []){ "bar" }; // b의 값은 1일 수 있음 (since C99)

비-const 타입의 포인터는 같은 타입이나 호환 타입(compatible type)의 const 한정 버전의 포인터로 암시적으로 변환될 수 있어요. 반대 방향 변환은 캐스트(cast) 표현식이 필요해요.

int* p = 0;
const int* cp = p; // OK: 한정자를 추가 (int를 const int로)
p = cp; // 오류: 한정자를 버림 (const int를 int로)
p = (int*)cp; // OK: 캐스트

T에 대한 포인터의 포인터는 const T에 대한 포인터의 포인터로 변환되지 않아요. 두 타입이 호환되려면 한정이 동일해야 해요.

char *p = 0;
const char **cpp = &p; // 오류: char*와 const char*는 호환 타입이 아님
char * const *pcp = &p; // OK, 한정자를 추가 (char*를 char*const로)

키워드 (Keywords)

const

주의 (Notes)

C는 const 한정자를 C++에서 받아들였어요. 하지만 C++과 달리, C에서 const 한정 타입의 표현식은 상수 표현식이 아니에요. 그래서 case 레이블로 쓰거나, static·thread 저장 기간 객체·열거자(enumerator)·비트 필드 크기를 초기화하는 데 쓸 수 없어요. 배열 크기로 쓰면 결과 배열은 VLA가 돼요.

더 알아보기