C - 1.4. 두번째 예제 마무리

1. 매크로 상수

1.3. 글에서 변수명은 숫자의 용도를 나타내주는 역할도 한다고 언급했었다. 그런데 1.3의 for문을 사용하는 코드에서 변수의 개수를 줄였으므로, 몇몇 숫자들의 용도를 쉽게 알 수 없게 되었다.

물론 1.3에서 본 코드는 매우 짧으므로 잘 아는 사람이 본다면 누구나 이 코드에서 300은 어떤 상한이며, 20은 각 스텝의 증분이구나! 라고 쉽게 생각할 수 있다. 그러나 만약 코드가 길고 복잡해지면 이렇게 상수의 의도를 파악하는 게 쉽지 않다.

이렇게 코드 내부에 특별한 이름 없이 그대로 쓰인 숫자를 '매직 넘버' 라고 하는데 실제로 이는 지양해야 할 습관이다.

그럼 우리는 어떻게 이러한 매직 넘버들의 용도를 나타내 줄 수 있을까? #define 을 이용한 매크로를 사용하면 된다. 가령 #define UPPER 300 이라고 하면 UPPER라는 문구를 자동으로 300으로 바꿔 주는 매크로를 정의한다는 뜻이다. 즉, UPPER 라는 말을 컴파일러로 하여금 300으로 알아듣게 한다.

구체적인 형식은 다음과 같다.

#define name replacement text
#define name replacement text

이러면 name 에 해당하는 게 나올 때마다 컴파일러에서는 replacement text의 내용으로 알아듣게 된다.


NOTE

구체적으로는 컴파일러가 동작하는 방식과 관련이 있다. 조금만 설명하면, 우리가 코드를 짜서 컴파일러에게 전달하면, 컴파일러에서는 코드를 읽어서 실행 파일을 만드는 동작을 하기 전에 코드에 대한 전처리를 시행한다. 이 부분에서는 헤더 파일의 내용 복사, 조건부 컴파일 처리 등등을 하는데 여기에는 매크로 처리도 있다.

즉 컴파일러가 실제로 읽고 컴파일하게 되는 코드는 우리가 정의한 #define 매크로가 모두 처리된 상태라는 것이다. 따라서 이렇게 정의한 상수들은 메모리를 차지하지 않는다. 매크로로 함수를 정의하더라도 마찬가지다. 코드가 실제로 어떤 동작을 하도록 해준 것이 아니라, 단순히 텍스트 치환을 해준 것이기 때문이다.


name은 변수의 작명과 똑같은 규칙을 가진다. 문자와 숫자의 조합이어야 하고 문자로 시작해야 한다. 그리고 replacement text는 어떤 문자열이라도 상관없다. 꼭 숫자일 필요는 없다는 뜻이다. 이 글의 뒷부분에서 코드로 보일 것이다.

2. 매크로를 이용한 코드

그러면 이러한 매크로를 이용해서, 상수들에 의미있는 이름을 부여해 보자.

#include <stdio.h>

#define LOWER 0
#define UPPER 300
#define STEP 20

/* 화씨 - 섭씨 표를 출력한다 */
main() {
    int fahr;

    for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) {
        printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32.0));
    }
}
#include <stdio.h>

#define LOWER 0
#define UPPER 300
#define STEP 20

/* 화씨 - 섭씨 표를 출력한다 */
main() {
    int fahr;

    for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) {
        printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32.0));
    }
}

이렇게 하면 LOWER, UPPER, STEP은 특정 숫자와 같으므로 실제 코드의 의미는 같다. 그러나 숫자들에 이름이 있기 때문에 사람이 알아보기 훨씬 편하다.

매크로문을 쓸 때에 생각할 점은 매크로의 이름(name)은 보통 일반 변수명과 구별하기 위해 영어 대문자만으로 쓰는 것이 일반적이라는 것, 그리고 매크로문에는 세미콜론(;)이 붙지 않는다는 점이다.

또한 replacement text에 붙은 이름은 꼭 하나일 필요 없다. 만약 300이라는 숫자가 프로그램의 다른 부분에서 다른 의미로 또 쓰이게 된다면, 이를테면 #define STUDENT_ID 300 과 같은 또다른 매크로를 정의해도 상관없다. 물론 같은 이름을 또 사용하는 건 안 된다.

3. 매크로의 활용

이 부분만 보면 그냥 변수를 사용하는 것과 무엇이 다른지 쉽게 알 수 없다. 물론 변수 할당에 드는 메모리를 절약할 수 있다고는 하지만 요즘은 거의 언제나 충분한 메모리가 있으므로 큰 의미는 없다.

그래서 변수와는 다른 느낌으로 쓰인 매크로를 하나 보겠다.

바로 위에서 쓴 코드에 있는 화씨-섭씨 환산식을 매크로로 대체할 수 있다.

#include <stdio.h>

#define LOWER 0
#define UPPER 300
#define STEP 20
#define FAHR_TO_CELSIUS(x) (5.0/9.0)*(x-32.0)

/* 화씨 - 섭씨 표를 출력한다 */
main() {
    int fahr;

    for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) {
        printf("%3d %6.1f\n", fahr, FAHR_TO_CELSIUS(fahr));
    }
}
#include <stdio.h>

#define LOWER 0
#define UPPER 300
#define STEP 20
#define FAHR_TO_CELSIUS(x) (5.0/9.0)*(x-32.0)

/* 화씨 - 섭씨 표를 출력한다 */
main() {
    int fahr;

    for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) {
        printf("%3d %6.1f\n", fahr, FAHR_TO_CELSIUS(fahr));
    }
}

어째서 위의 매크로에서는 x 에 대해서 정의한 매크로인데 fahr 에 대해서도 잘 작동하는지는 적당히 넘어가자. 중요한 건 코드가 잘 작동하고, 이런 식으로 매크로를 일종의 함수와 비슷한 동작을 하도록 만들 수 있다는 사실이다. 이건 우리가 지금까지 배운 일반적인 변수로는 할 수 없는 일이다.

사실 우리나라에서는 화씨 온도를 잘 쓰지 않으므로 $ \frac{5}{9} \times (F - 32) $ 라는 환산식은 그리 익숙하지 않다. 하지만 저렇게 환산식에 이름을 부여하면, 저 함수를 쓰면 화씨 온도에서 섭씨 온도로 환산할 수 있다는 것을 훨씬 쉽게 알아볼 수 있는 것이다.