C 언어 | 전처리(preprocess) | 매크로 함수 - #define

매크로 함수는 #define 지시어와 매크로 전개로 매개 변수를 받을 수 있으며, 임의의 텍스트를 인수 매크로 전개할 수 있다. 이것으로 함수와 같이 행동 매크로를 만들 수 있지만, 어디까지나 전처리에 의해 컴파일 전에 전개되는 텍스트이므로 함수 호출 오버 헤드가 발생하지 않는다. 간단한 계산 처리의 전개에 적합하다.

매개 변수가 있는 매크로 전개

여러번 사용되는 계산식 등은 함수로 정리하여 여러번 재작성 작업에서 해방되지만, 간단한 계산식 함수화는 다른 문제를 생긴다. 함수를 호출하려면 함수의 매개 변수에 인수를 전달하는 값을 복사하여 스택에 저장하는 작업이 발생한다. 매개 변수가 없는 함수에서도 제어가 호출자에게 반환하기 위해, 기계어 레벨에서는 주소의 저장이 이루어지고 있다.

빠른 프로그램을 실현하기 위해서는 이러한 불필요한 처리는 적게해야 한다. 그러나 함수를 사용하지 않는 프로그램은 매우 비효율적이고 재사용이 되지 않는다. 간단한 계산을 효율적으로 기능할 수 있는 방법은 없는 것일까?

그래서 간단한 계산식 등은 변수 마찬가지로 컴파일시에 전개해 버리는 수법이 적용된다. 즉, 전처리를 사용한다. #define은 토큰 열에 이름을 붙여서 컴파일시에 전처리에 의해 이것이 대체하는 것이었다. 상수가 아닌 계산식에 이름을 붙여 소스 내에 필요한 곳에 사용하고, 컴파일시에 대체하여 효율적으로 빠른 프로그램을 만들 수 있다.

#define는 매크로 함수라고 하는 인수를 받을 수 있는 대체 처리를 지원하고 있다. 이것은 이용자로부터 보면 함수와 같은 동작을 한다. #define가 인수 대체를 지원하려면 다음과 같은 구문을 사용한다.

#define 지시문 (매크로 함수)

#define 식별자(인수1, 인수2 ...) 토큰열

이 매크로 함수를 보면, 식별자가 함수 이름이며, 토큰 열의 형식이 반환값처럼 느껴질 것이다. 그러나 매크로는 인수를 받는 것은 아니다. 단순히 토큰 열에 대해 인수를 전개할 뿐이다. 예를 들어, 다음 매크로 함수를 보도록 하자.

#define ADD(a , b) a + b

이것은 ADD() 매크로 함수를 정의하고 있다. ADD(10, 20)과 소스 내에 작성하면, 그 위치에 전처리가 10 + 20이라는 텍스트로 바꾼다. 매크로 함수의 개발자는 함수가 호출되는 것이 아니라, 단순히 그 위치에 토큰 열이 전개될 뿐임을 의식하지 않으면 안된다.

코드1

#include <stdio.h>
#define MUL(multiplicand , multiplier) multiplicand * multiplier

int main() {
  printf("5 * 5 = %d\n" , MUL(5 , 5));
 return 0;
}

코드1은 곱셈을 하는 MUL()라는 매크로 함수를 정의하고 있다. MUL() 매크로 함수의 이용자에게는 이것이 매크로 함수임을 특별히 의식할 필요가 없다. 매크로 함수의 개발자는 이 매크로 함수의 사양을 다른 함수처럼 동일하게 정할 수 있다.

그러나 여러번 말하듯이 매크로 함수의 개발자는 매크로 함수는 텍스트 레벨에서 대체하는 것이며, 실제 함수처럼 실행시에 호출되는 것은 아니라는 것을 깨달아야 한다 . 사실은 코드1의 MUL() 매크로 함수는 일반 함수에서는 발생하지 않는 큰 문제가 포함되어 있다. 이것은 다음의 프로그램을 실행하면 이해할 수있는 것이다.

코드2

#include <stdio.h>

#define MUL(multiplicand , multiplier) multiplicand * multiplier
int mul(int multiplicand , int multiplier) {
 return multiplicand * multiplier;
}

int main() {
  printf("메크로 함수 : MUL(3 + 2 , 5) = %d\n" , MUL(3 + 2 , 5));
 printf("일반 함수 : mul(3 + 2 , 5) = %d\n" , mul(3 + 2 , 5));
  return 0;
}

코드2는 곱셈을 하는 매크로 함수 MUL()와 같은 처리를 하는 일반 함수 mul()가 정의되어 있다. main() 함수에서 이 두 함수에 같은 인수 값을 주고 결과를 표시하고 있지만, 이들은 다른 값을 반환한다. 일반 함수가 반환한 값이 25이면 개발자가 의도한 값 것이다. 그러나 매크로 함수는 13이라는 이상한 계산 결과를 반환한다. 왜일까요?

함수는 함수가 호출되기 전에 인수에 지정된 표현식이 계산된다. 따라서 코드2는 3 + 2 표현식이 계산되어 최종적으로는 mul(5, 5)의 형태로 함수가 호출된다.

그러나 매크로 함수는 텍스트 레벨의 간단한 교체 작업 때문에 이런 식의 계산이 수행되지 않는다. MUL(3 + 2, 5)라는 매크로 지정은 3 + 2라는 텍스트가 multiplicand로 전개되기 때문에 컴파일시에는 3 + 2 * 5라는 식으로 전개되고 있는 것이다. 따라서 연산자의 우선 순위에 따라 2 * 5가 먼저 계산된 다음에 10 + 3이 계산된다. 그 결과 MUL(3 + 2, 5)는 13을 반환된다.

이러한 문제를 해결하기 위해서는 매크로 함수의 인수에는 반드시 ()를 지정하여 우선 순위를 보호하는 방법이 있다. MUL() 함수는 다음과 같이 정의한다.

#define MUL(multiplicand , multiplier) ((multiplicand) * (multiplier))

이와 같이 괄호로 둘러싸여, 전개 후에 계산 순서를 보호할 수 있다.

마지막으로, 대표적인 매크로 함수의 실전 방법을 보여준다. 매크로 함수가 위력을 발휘하는 것은 복잡한 수식을 단순화하는 경우이다. 행정 절차와 세제가 복잡하다면 주민이 기피하듯이, 당신이 만든 시스템의 사양이 아무리 효율적이라도, 계산 처리 등에 시간이 걸린다면 개발자는 시스템 서비스를 사용하고 싶은 생각하지 들지 않을 것이다.

예를 들어, 32비트 시스템에서 RGB 값이 각 요소 8비트씩으로 표현하는 시스템의 경우, 색의 요소에 액세스하기 위해서는 시프트 연산 등을 실시하여 특정 바이트를 추출해야 한다. 이러한 연산 처리는 매크로 함수가 자랑하는 분야이다.

코드3

#include <stdio.h>

typedef unsigned char BYTE;
#define RGB(r , g , b) ((BYTE)(r) << 16) | ((BYTE)(g) << 8) | (BYTE)(b)
#define RED(color) (BYTE)((color) >> 16)
#define GREEN(color) (BYTE)((color) >> 8)
#define BLUE(color) (BYTE)(color)

int main() {
 int color = RGB(0xFF , 0xEE , 0xAA);
  printf("R = %X : G = %X : B = %X\n" ,
    RED(color) , GREEN(color) , BLUE(color));
 return 0;
}

코드3는 색깔을 나타내는 숫자를 만들기 위한 매크로 함수 RGB()와 빨강, 녹색, 파랑의 각 요소의 값을 추출하기 위한 매크로 함수 RED(), GREEN() BLUE()를 정의하고 있다. 이 프로그램은 32비트 컴퓨터에서 작동하며, 하위에서 차례로 파랑, 녹색, 빨강의 요소 값이 8비트에 대등한 컬러 정보를 취급하는 것을 상정하고 있다.

단일 수치로 여러 정보를 보유하는 것은 드문 일이 아니다. 이러한 사양이 주어진 경우는 데이터의 생성과 단일 정보를 얻기 위해 비트 연산이 필요하다. 그러나 여러번 그것을 작성하는 것이 번거롭고, 복잡한 프로그램은 단순한 실수를 유발할 수 있다. 그래서 코드3과 같이 매크로 함수를 사용하여 문제를 단순화한다.

RGB() 매크로는 r에 빨간색 요소를 g에 녹색 요소를 b에 파란색 요소를 각각 1바이트로 지정해야 한다. 매크로 함수는 이러한 인수를 바탕으로 각 요소를 적절한 위치로 이동하는 식으로 전개한다. RED(), GREEN(), BLUE() 매크로 함수의 color는 RGB() 매크로 함수에서 만든 32비트 숫자 형식을 전달한다. 이 매크로 함수들은 색상 정보에서 원하는 1바이트를 추출하는 식으로 전개한다.

이 외에도 함수 호출을 은폐하는 매크로 함수(매크로 함수에서 함수를 호출하여 절차를 간소화하기) 등의 이용도 된다. 이처럼 매크로 함수는 계산만으로 구성되는 비교적 간단한 변환 처리 등에 많이 사용된다.




최종 수정 : 2017-11-26