6-4-나.#define

#define전처리문은 매크로 상수를 정의하는데 기본 문법은 다음과 같다.

 

#define 매크로명 실제값

 

기억하기 쉬운 적당한 이름을 주고 실제값을 뒤에 써 주면 되는데 예를 들어 YEAR라는 매크로 상수를 365로 정의하고 싶다면 #define YEAR 365라고 정의하면 된다. 그렇다면 매크로 상수는 과연 무엇이며 왜 이런 것이 필요한지 알아보자. 다음 예제는 시속(Km/h)을 입력하면 이 속도가 마하로 환산해서 얼마인가를 계산해 준다.

 

: define

#include "stdio.h"

#define MACH 1200.0

 

void main()

{

     int speed;

 

     printf("속도를 입력하세요(Km/h) :");

     scanf("%d",&speed);

     printf("이 속도는 마하 %f입니다.\n",speed/MACH);

}

 

참고로 마하란 소리가 매질속을 통과하는 속도인데 온도에 따라 조금씩 달라지지만 0도일 때 초속 331.5m이고(상온에서는 통상 340m) 이를 시속으로 환산하면 대략 1200Km/h 정도가 된다. 그러니까 마하 1은 1시간에 1200Km를 달리는 정도의 속도이며 대한항공의 보잉 747기는 시속 600Km/h 정도로 비행할 수 있으므로 마하로 환산한 속도는 0.5가 된다. 마하에 대한 더 자세한 사항은 계몽사 학생 대백과 사전을 참조하기 바란다.

이 예제는 Km/h 단위의 시속을 입력받아 이 속도를 마하로 환산하여 출력하는데 입력된 시속을 1200.0으로 나누면 된다. 소수점 이하까지 정확하게 나누기 하기 위해 1200 이라는 정수 상수를 쓰지 않고 1200.0이라는 실수 상수를 사용했다. 실행 결과는 다음과 같다.

 

속도를 입력하세요(Km/h) :600

이 속도는 마하 0.500000입니다.

 

이 예제에서 사용된 MACH라는 명칭이 바로 매크로 상수이다. 이 값이 1200.0으로 정의되어 있고 전처리기는 소스에 사용된 MACH라는 명칭을 컴파일하기 전에 모두 실제값인 1200.0으로 치환한다. 그러므로 speed/MACH는 speed/1200.0으로 치환되어 Km/h의 속도가 마하로 환산되는 것이다. #define 전처리문은 1200.0이라는 상수에 MACH라는 이름을 붙이는 역할을 한다.

그렇다면 그냥 1200.0을 바로 쓰는 것과 MACH라는 매크로 상수를 정의하여 사용하는 것과는 어떤 차이점이 있을까? 컴파일된 결과는 아무 차이가 없지만 상수에 이름을 붙이면 소스를 읽기가 훨씬 더 쉬워지고 관리하기도 수월해진다. 만약 1200.0이라는 상수를 바로 사용했다고 하자. 누군가가 이 소스를 볼 때 1200.0이 도대체 무슨 의미인지, 왜 이 값으로 speed를 나누는지 금방 이해하기 어려울 것이다. 소리가 1시간동안 공기중을 날라가는 속도가 1200Km라는 것을 어떻게 짐작하겠는가?

1200.0이라는 상수를 직접 쓰는 대신 MACH라는 매크로 상수를 쓰면 이 값이 마하 상수를 의미한다는 것을 쉽게 짐작할 수 있다. 즉, 매크로는 상수에 이름을 붙여 사람의 부족한 기억력을 도와준다. 3.1415라는 값보다는 PI라는 매크로 상수가 더 이해하기 쉽고 31536000이라는 상수에 ONEYEARSECOND라는 이름을 붙여 놓으면 1년간의 초라는 것을 쉽게 파악할 수 있다. 물론 매크로의 이런 도움을 받으려면 기본적인 영어 실력을 갖춰야 하는데 MACH를 "마치"나 "마크"로 읽는 사람에게는 별반 도움이 되지 않을 것이다. 그래서 프로그래머는 영어도 잘해야 한다.

매크로 상수는 기억의 용이함을 위해 사용되기도 하지만 특정값의 일괄적인 수정을 위해서도 유용하게 사용된다. 예를 들어 게임을 제작하는데 이 게임의 제한 시간이 240초로 설정되어 있다고 하자. 게임의 곳곳에서 제한 시간을 점검하기 위해 240이라는 상수를 사용할 것이다. 그런데 막상 게임을 출시하고 보니 240이라는 제한 시간이 너무 짧으니 이 값을 360으로 늘려 달라는 고객의 요청이 들어왔다.

이렇게 되면 소스의 모든 곳을 다 뒤져 제한 시간 240이 사용된 곳을 일일이 눈으로 찾아 360으로 고쳐야 한다. 소스를 수정하기 번거롭기도 하지만 혹시 실수로 하나를 빼 먹으면 불일치가 발생하여 버그의 원인이 되기도 한다. 이럴 경우 애초에 240이라는 상수를 바로 사용하지 말고 다음과 같이 매크로 상수를 정의해서 사용했다면 수정 작업이 아주 쉬워진다.

 

#define GAMETIME 240

 

이 매크로 상수만 360으로 바꾸면 #define 전처리기가 소스의 모든 GAMETIME을 360으로 일괄 치환하므로(one touch) 수정하기도 간편하고 실수로 하나를 빼먹고 고치지 않을 위험도 없어진다. 이처럼 두 번 이상 사용될 가능성이 있는 상수라면 처음부터 매크로 상수를 정의해서 사용하는 것이 좋다.

다음은 #define 문으로 매크로 상수를 정의할 때의 일반적인 주의 사항이다. 상식적으로 쉽게 이해될 것이다.

 

#define문은 전처리문이지 코드를 생성하는 명령이 아니다. 그래서 행 끝에 세미콜론은 붙이지 않는다. #define MACH 1200.0;이라고 써서는 안된다. 만약 이렇게 쓰면 세미콜론조차도 매크로의 실제값에 포함되어 버린다. #include는 물론이고 다음에 배울 모든 전처리문들도 세미콜론은 붙이지 않으며 주석을 제외한 다른 문장이 뒤따라 올 수 없다.

매크로의 이름도 일종의 명칭이기 때문에 명칭 규칙에 맞게 작성해야 한다. 중간에 공백이 들어간다거나 숫자로 시작한다거나 다른 명칭과 충돌해서도 안된다. 매크로 상수는 다른 명칭과 구분될 수 있도록 관습적으로 대문자를 사용하는 것이 일반적이다. NUM, MY_MACRO 등은 적법한 명칭이며 3RD는 숫자로 시작되었으므로 부적격하며 MY MACRO는 중간에 공백이 있으므로 매크로명으로 사용할 수 없다.

매크로 이름에는 공백이 들어갈 수 없지만 매크로의 실제값은 공백을 가질 수 있다. #define 전처리문은 매크로를 실제값으로 단순 치환할 뿐이므로 공백이 있건 한글을 사용하건 전혀 상관하지 않는다. 다음은 자주 쓰는 메시지 문자열을 매크로로 정의한 것이다.

 

#define ERRMESSAGE "똑바로 하란 말이야"

 

문자열을 통째로 ERRMESSAGE라는 매크로 상수로 정의했으며 좀 어색해 보이지만 적법한 문장이다. 실제 코드에서 puts(ERRMESSAGE); 형식으로 사용하면 된다.

문자열 상수내에 있는 매크로나 다른 명칭의 일부로 포함된 경우는 치환되지 않는다. #define HUMAN 5 라는 전처리문은 소스의 모든 HUMAN을 찾아 5로 치환할 것이다. 그러나 printf("I am a HUMAN"); 문장에 있는 HUMAN은 매크로 상수와 이름은 같지만 어디까지나 문자열일 뿐이므로 치환하지 않는 것이 합리적이다. HUMANNAME이라는 다른 명칭에도 HUMAN이라는 이름이 포함되어 있지만 분리된 명칭이 아니므로 5NAME으로 치환되지 않는다.

매크로는 중첩 가능하다. 즉, 매크로 상수가 매크로 상수를 참조할 수 있다는 얘기다. 다음 예를 보자.

 

#define A 3

#define B (A*2)

 

A를 3으로 정의했고 B는 A에 2를 곱한 값으로 정의했으므로 6이 될 것이다. B가 A를 참조하고 있는 셈이다. 두 번 세 번 얼마든지 중첩할 수 있되 단 중첩되는 매크로가 먼저 정의되어 있어야 하므로 순서에 신경써야 한다.

값을 가지지 않는 빈 매크로도 정의할 수 있다.

 

#define PROFESSIONAL

 

이 매크로는 값을 가지지 않으며 매크로 상수 자체만 존재할 뿐이다. 이렇게 값을 가지지 않는 매크로는 주로 조건부 컴파일 지시자와 함께 사용되며 존재하는가 아닌가만으로 의미를 가진다. 또한 아무 값도 가지지 않음을 명시할 때도 빈 매크로를 정의한다.

 

#define 전처리문의 동작이 단순하기 때문에 매크로 상수는 쉽게 이해가 갈 것이다. 그렇다면 매크로 상수를 어느 수준으로 사용할 것인가에 대해 생각해 보자. 모든 상수를 다 매크로 상수로 정의한 후 사용할 수도 있고 적당한 수준에서 상수를 직접 사용할 수도 있는데 이는 개인적인 취향에 따라 결정할 문제이다.

매크로 상수는 분명히 기억하기 쉽도록 해 주고 일괄적인 수정에 도움을 준다. 그러나 너무 남발하면 오히려 소스를 더 읽기 어렵게 만들 수도 있으며 기억력 보조에도 별 도움이 되지 못하는 경우가 있다. 다음 예를 보자.

 

#define A 5

#define B (A+4)

#define C (B*2+A)

#define D (C+(A*B))

 

printf("결과는 %d",func(D));

 

이쯤되면 결과로 어떤 값이 출력될 것인가를 예측하기 어렵다. 더구나 실제 소스에서는 매크로를 정의하는 곳과 사용하는 곳이 굉장히 멀리 떨어져 있기 때문에(MFC가 그렇다) 일일이 매크로 정의부를 찾아 보기도 번거롭다. D가 어떤 값인지 알아보려면 먼저 C의 정의문을 알아야 하고 C를 알려면 A, B의 값을 먼저 조사해야 한다. 위 예는 억지로 만들어진 것이지만 실제 큰 프로젝트를 분석하다 보면 훨씬 더 심한 매크로의 중첩을 심심치 않게 볼 수 있을 것이다.

어느 수준으로 매크로 상수를 활용할 것인가는 프로젝트의 성격에 따라 결정하되 나는 개인적으로 일괄적인 수정의 용이함을 위해서는 매크로를 사용하지만 기억의 용이함을 위해서는 사용하지 않는 편이다. 이런 목적이라면 매크로 상수를 쓰는 것 보다는 const나 열거형, 아니면 상수옆에 짧은 주석을 달아 놓는 방법을 더 많이 애용한다.

 

speed/1200.0                   // 여기서 1200.0은 음속을 의미한다.

 

아무래도 영어로 된 매크로 이름이 한글로 된 주석보다 더 설명적일 수는 없다. 4장의 movesharp 예제는 커서 이동키의 키 코드 75, 77, 72, 80을 곧바로 사용하는데 숫자를 바로 쓰는 것보다는 다음과 같이 매크로 상수를 정의한 후 사용하는 것이 더 좋다.

 

#define LEFT 75

#define RIGHT 77

#define UP 72

#define DOWN 80

 

이렇게 이름을 붙여 놓으면 더 이상 헷갈리지 않을 것이며 사용하기도 쉽다. 또는 다음과 같은 열거 멤버를 정의하여 사용할 수도 있다.

 

enum { LEFT=75, RIGHT=77, UP=72, DOWN=80 };

 

열거형의 태그도 선언하지 않았고 변수도 만들지 않았다. 이렇게 되면 열거 멤버의 상수값만 사용하는 셈인데 일련의 값에 대해 이름을 붙일 때는 이 방법도 나름대로 쓸만하다.