C 언어 | 구조체 선언 | 형식 한정자 - const

값을 변경할 수 없는 변수를 선언는 const 형식 한정자와 컴파일러에 최적화를 거부하는 volatile 형식 한정자를 소개한다.

상수화

지금까지 선언은 기억 클래스 지정자(static 등), 형식 지정자(int, char 등), 선언자(변수명, 함수명 등), 초기화(변수의 초기 값)을 조합하여 실시하는 라는 것을 설명했다. 형식 한정자는 이 외에도 선언 대상의 성질을 결정하는 키워드를 제공한다. 이것을 선언 이외에 감안할 때, C 언어 선언 구문은 항상 다음과 같은 형태로 간주할 수 있다.

선언

기억클래스지정자 형지정자 형한정자 선언자 초기화 ...;

기억 클래스 지정자, 형 지정자, 형식 한정자(type qualifier)는 어떤 순서도 상관없다. 또한 기억 클래스 지정자, 형 한정자, 초기화(Initializer)는 생략할 수 있다. 조합은 형식 지정자를 생략할 수 있다. 선언자는 변수의 이름 등을 지정하며, 지금까지 “식별자"라고 하였다.

형식 한정자에서 가장 많이 사용되는 것은 const 키워드이다. 이것은 초기화 이후에 이 변수가 변경되지 않는다는 것을 나타낸다.

const int ciValue = n;

이 경우는 정수형의 변수 ciValue는 앞으로 계속해서 저장한 n값이 변경되지 않는 것을 나타낸다. 만약 프로그램에서 이 변수의 내용을 바꾸려고 하면 컴파일 오류가 발생한다. 덧붙여서 const 형식 한정자를 이용한 선언으로 형식 지정자를 생략한 경우는 int 형으로 해석된다.

예를 들어 “Kitty"라는 문자열을 프로그램의 많은 곳에서 사용하는 경우, 그 때마다 리터럴 문자열을 지정하는 것은 매우 비효율적인 코드이다. 어느 날, 프로그램을 개선하기 위해 문자열을 “Kitten"로 변경해야 되면, 모든 리터럴 문자열을 찾아 변경해야 한다. 만약 1개라도 변경하는 것을 놓친다면, 버그가 될 가능성이 있다. 즉, 무결성이 낮은 프로그램이 되고 마는 것이다. 이것은 계산용의 정수형 상수를 작성하는 경우에도 마찬가지이다.

그래서 당신은 자주 사용되는 정보는 하나의 전역 변수 등을 초기화하고, 이를 여러 곳에서 사용할 수 있다. 이렇게 하면 프로그램의 사양 변경시에 수정하는 소스는 적어지고 정합성이 유지된다. 그러나 이렇게 되면 새로운 불안한 것이 생긴다. 변수는 누군가가(어떤 함수가) 잘못 변경되어 버릴 가능성이 있다. 그래서 const를 사용하여 변수를 보호한다.

많은 곳에서 참조되고, 변경되는 것을 원하지 않는 정적인 정보라고 하면 응용 프로그램의 이름과 버전 정보, 회사 이름 등일 것이다. 이러한 정보를 응용 프로그램이 보유하는 것은 매우 일반적이다.

코드1

#include <stdio.h>

typedef unsigned char BYTE;
const char APPNAME[] = "Kitty on your lap";
const VERSION = (15 << 8) | 7;

int main() {
  /* APPNAME[1] = 'X'; /*error*/
  /* VERSION = 0; /*error*/

 printf("APPNAME = %s\n" , APPNAME);
  printf(
   "VERSION = %d.%d\n" ,
    (BYTE)VERSION , (BYTE)(VERSION >> 8)
  );
  return 0;
}

코드1은 문자 배열의 변수 APPNAME와 정수 변수 VERSION를 외부 레벨에서 선언한다. 이러한 변수는 const 형 한정자가 지정되어 있기 때문에 상수처럼 그 값은 항상 동일하다. 이러한 변수는 표현식에서 참조는 일반 변수처럼 할 수 있지만, 대입할 수 없다. 주석 처리되어 있는 대입식을 컴파일하면 오류가 발생하는 것을 확인할 수 있을 것이다.

APPNAME 변수처럼 배열에 const를 지정한 경우, 배열의 각 요소에 대해 const가 걸린다. 또한 VERSION 변수처럼 const 형 한정자만 선언하면 const int 형으로 해석된다. VERSION은 하위 8비트에 메이저 버전을 다음의 8비트에 마이너 버전을 포함하는 16비트 정수 정보로 취급하고 있다.

이것은 const를 이용한 예이다. 이 밖에도 많은 곳에서 const는 이용된다. 예를 들어, 함수가 주어진 포인터를 변경하지 않는다면, 그것을 개발자에게 명시적으로 전달 수단으로 const 형의 인수를 선언하는 방법이 있다. 포인터 형에 const 형 한정자가 지정되어 있는 경우는 그 포인터가 아닌 포인터가 가리키는 값을 변경하지 않는 것을 나타낸다.

리터럴 문자열에 대한 포인터에서 간접 참조를 하며, 리터럴 문자열 값을 변경하려고 하는 경우의 결과는 부정이다. 이것은 어떤 개발자도 선호하지 않지만 구문에 틀린 것이 아니기 때문에 버그의 원인이 될 수 있다. 그래서 리터럴 문자열에 대한 포인터를 만들 때 경험있는 프로그래머는 const를 습관적으로 지정한다.

코드2

#include <stdio.h>

void Function(const char *str) {
  /* str[0] = 'X';      /* error */
 /* str = "당신의 무릎 위에 고양이"; /* OK */
  printf("%s\n" , str);
}

int main() {
 Function("Kitty on your lap");
  return 0;
}

코드2의 Function() 함수는 const 형 한정자를 가지는 문자열에 대한 포인터를 받는다. 포인터는 간접 참조를 사용하여 포인터가 가리키는 주소의 값을 변경할 수 없다. 그러나 const 포인터가 보유한 주소 자체는 변경할 수 있기 때문에 str 매개 변수에 다른 주소를 대입할 수 있다.

이와는 반대로 포인터를 상수화하고 싶은 경우가 있을 것이다. 즉, 포인터가 참조하는 변수의 값은 변경될 수 있으나, 포인터 자체는 변경해서는 안된다는 포인터이다. 코드2와 같이 const char *str와 같이 선언하면, 상수 데이터 포인터로 해석되기 때문에 포인터 자체는 고정되지 않는다. 즉, 다음과 같은 특성을 가지고 있다.

const char *str = buf1; /* 상수의 포인터 */
*str = 'a'; /* error */
str = buf2; /* OK */

상수 포인터는 간접 참조에 의해 참조된 값을 변경할 수 없는 포인터이며, 포인터 자체가 상수라는 것은 아니다. 포인터 자체를 상수로 선언하려면 const 키워드의 위치를 * 후에 가지고 있다. 즉, char *const str와 같이 선언한다.

 char *const str = buf1; /*상수 포인터*/
*str = 'a'; /* OK */
str = buf2; /* error*/

상수에 대한 포인터와 상수 포인터는 선언이 비슷하지만 이질적인 것이므로, 그 차이를 인식할 수 있게 하자.

이와 같이 const는 다양한 상황에서 사용된다. 특히 값을 변경할 수 없는 변수에 대해서는 적극적으로 const를 이용해야 한다고 생각된다. 이렇게 함으로써 시스템은 변수를 읽기 전용 메모리 영역에 배치시켜보다 빠른 동작을 실현할 수있는 가능성이 있기 때문이다. 물론 이러한 최적화 처리는 컴파일러와 시스템에 의존하는 문제이므로 개발자가 의도하는 것은 아니다.

최적화 거부

const에 이어서 또 하나의 형식 한정자 volatile 키워드가 존재한다. 이 형식 한정자는 상당히 특별한 소프트웨어 및 시스템 개발을 제외하고 사용하지 않을 것이다. volatile은 값이 항상 가변이며, 시스템이 최적화 처리를 하지 않도록 명시적으로 나타낸다. 다음 문장은 volatile을 지정된 숫자형 변수의 선언이다.

volatile int viValue = n;

volatile 형 한정자를 지정하는 경우는 const 마찬가지로 형식 지정자를 생략할 수 있다. 형식 지정자가 생략된 경우 volatile int로 해석된다.

volatile은 이를 실행하는 프로그램이 아닌 미지의 프로세스(OS 나 특수 하드웨어 등)이 변수를 사용할 경우, 컴파일러가 마음대로 의도하지 않은 모양으로 최적화하는 것을 방지한다. 물론 많은 경우는 외부 프로세스가 값을 변경하는 것을 원하지 않기 때문에, 독립된 소프트웨어는 volatile을 지정할 수 없다.

코드3

#include <stdio.h>

volatile viVariable = 0xFF;
const volatile char *str = "Kitty on your lap";

int main() {
  printf("viVariable = %d\nstr = %s\n" , viVariable , str);
 return 0;
}

이 프로그램에서는 volatile 형 한정자를 지정하는 정수형 변수 viVariable와 문자열에 대한 포인터 str을 선언하고 있다. 이러한 변수는 다른 권한있는 프로그램이 변경될 가능성을 보여주고 있지만, 당연히 시스템과 하드웨어와 통신을 하지 않으면 변경될 수도 없고, 이 프로그램의 volatile 형 한정자는 실질적인 의미가 없다.

덧붙여서 str 변수의 선언을 보고 알 수 있듯이, const와 volatile 형 한정자는 동시에 지정할 수 있다. 이 경우 const가 지정되어 있기 때문에 이 프로그램에서 변수의 내용을 변경할 수 없음을 나타낸다. 그러나 시스템이나 하드웨어의 변경은 받아 들일 것을 의미한다.




최종 수정 : 2017-11-26