본문
C의 기억부류(Storage Class)와 변수 #3. 타입 한정자(Type Qualifier)
새로운 제목으로 글을쓰려다 그냥 3번째 연재글로 올리기로 했다. 앞의 두 글은 기억부류를 지정하는 auto, extern, static, register의 4가지 지정자(Specifier)에 대해 다루었다. 변수의 특성을 결정하는데에는 데이터 타입(데이터형)과 기억부류가 있는데, C90에서는 여기에 불변성(constancy)과 휘발성(volatility)이라는 특성이 추가되었다. 이 둘을 위하여 const, volatile 라는 타입 한정자(Type Qualifier)가 사용되며, 이 키워드로 변수를 한정된 데이터형(qualified type)으로 만들 수 있다. 또한 추가로, C99에서는 컴파일러 최적화를 위해 세번째 한정자로 restrict를 추가하였다. 즉 const, volatile, restrict 세가지 타입한정자가 존재한다.
책에서는 데이터형 한정자(Data Qualifier)라는 표현을 사용했는데, 타입 한정자라는 단어가 더욱 익숙하기 떄문에 타입 한정자라는 용어를 사용하기로 한다. 한정자/Qualifier라는 의미가 명확하게 와닿지 않아서 영어사전을 확인해보니 예선통과자라는 뜻이 가장 먼저 나오는데 이건 아닌것같고, 같이 검색된 프랑스어 뜻을 참고하면 '형언/수식' 의 의미인듯하다. 이 뜻을 반영해보자면 '데이터에 부가적인 정보를 제공하기 위한' 키워드, 정도가 되지않을까 싶다.
* Const 타입한정자
변수선언에서 const 키워드는 해당 변수를 대입/증가/감소 연산으로 값을 변경할 수 없는 변수로 만든다, 따라서 선언시에만 값 초기화가 가능하다. const 키워드를 일반 변수 선언에 사용할때에는 크게 걱정할것은 없지만, 포인터변수 선언시에는 위치에 따라서 그 의미가 달라지므로 주의해야한다. 즉, 포인터 '주소' 자체를 const로 만드는것과, 포인터가 가리키는 '값'을 const로 만드는 상황을 구분해야 한다. 예를 들어 다음과 같은 코드를 생각할 수 있다.
1. const float * pf;
- pf는 항상 상수로 유지해야하는 float형 '값'을 가리키는 포인터변수다. pf가 가리키는 주소는 변경될 수 있다. 즉, pf가 다른 const 값을 가리키도록 pf의 주소를 변경할 수는 있다. (*pf)++ 불가능
2. float * const pt;
- 포인터 pt의 자체의 값(주소)을 변경할 수 없다. 즉, pt는 항상 같은 주소를 가리킨다. 그러나 pt가 가리키는 값은 변경할 수 있다. 포인터 변수 pt의 주소를 상수화 시킨다는 의미이다. (*pt)++; 가능
3. const float * const ptr;
- ptr은 항상 같은 주소를 가리키고 그 위치에 저장된 값도 변경할 수 없다.
4. float const * pfc;
- const float * pfc;와 같다. 데이터형 이름과 * 사이에 const를 넣는것은 그 포인터를 사용하여 그것이 가리키는 값을 변경할 수 없다는 것을 의미한다. 즉, const를 * 왼쪽 어딘가에 넣으면 그 '값'을 상수로 만들고 *의 오른쪽에 넣으면 그 포인터 자체('주소')를 상수로 만든다.
함수 선언에서의 const의 의미또한 알아둘 필요가 있다. 1. 파라미터로 넘어간 변수의 값을 변경할 수 없다, 2. 리턴된 '값'은 변경할 수 없다. 라는 두가지의 의미와 함께, C++에 추가된 클래스 개념을 위해 3. 클래스 멤버변수값을 변경할 수 없다. 의 세가지의 의미가 있다. 클래스 멤버 변수에도 const를 붙일 수 있는등, 클래스에서 const 사용 시 고려할점이 더 있는데 이와 관련해서는 이 글('[C++ 언어] 제 6 강 : static, const 맴버') 이 참고하기 좋아보인다.
1. void display(const int array[])
- display 함수는 호출함수에 있는 '데이터'를 변경할 수 없게된다.
2. const int* f() { return &i; }
- f()가 반환하는 값은 반드시 const int* 변수에 대입되어야 한다. 이것은 f()의 반환 값이 추후에라도 변경되는것을 방지한다.
3. void CTest::Print() const;
- 멤버함수의 괄호 뒤에 const를 붙이면 해당 함수는 클래스(CTest) 내의 어떠한 멤버변수도 값을 변경시킬 수 없다. (포인터형을 리턴할수도 없다)
또한 프로그램에서 사용할 상수를 선언할 떄, 전역변수를 사용하다보면 실수로 데이터가 변경될 수 있으므로 이를 방지하기위해 const를 사용하기도 한다. 상수를 선언하는데에는 두가지 방법이 있는데, 1. 전역변수형태로 const double PI=3.14; 로 하고 외부 파일에서 extern const double PI;를 사용할수도 있고, 아니면 2. 헤더 파일에 static const double PI = 3.14;로 선언하고 c 파일에서 헤더파일을 include하여 사용할 수 있다. 헤더파일방식으로 상수변수를 선언했을때의 장점은 한 파일에 전역변수를 선언하고 나머지 파일들에 extern으로 참조선언을 추가로 해줄 필요가 없이 그저 모든 파일들이 동일한 헤더파일을 include하면 된다는것이다. 대신 단점으로 데이터가 중복되어 저장된다는 점이 있다.
전역변수의 형태로 헤더파일에 상수를 정의할때, 만약 static을 사용하지않은 채(즉, double PI) c파일에서 include하는경우, 동일한 변수의 정의선언이 각 파일에 들어가는 결과를 가져온다. 즉, 각 변수들이 '외부연계 정적변수'로 사용되어 별개의 데이터 복사본이 각각의 파일에 하나씩 제공된다. 따라서 이름이 동일하더라도 전혀 다른 변수로 인식되고 사용되기 때문에 파일간의 커뮤니케이션에 그 데이터를 사용하면 제대로 동작하지 않을 수 있다. 그러나 const라도 사용하게되면(즉, const double PI) 해당 변수의 값이 변하지 않으므로 조금 더 안정적인 실행을 기대할 수 있다(물론 static const 둘다 사용하는것이 최선이다).
내용출처: Stephen Prata, 'C 기초플러스(C Primer Plus)' 12장.
댓글