매크로 함수
매크로 함수란?
C언어에서는 #define 선행처리 지시문에 인수로 함수의 정의를 전달함으로써, 함수처럼 동작하는 매크로를 만들 수 있습니다.
이러한 매크로를 함수 같은 매크로(function-like macro) 또는 매크로 함수라고 합니다.
다음 예제는 SUB(X, Y)와 PRT(X)라는 매크로 함수를 정의하고 사용하는 예제입니다.
예제
#include <stdio.h>
#define SUB(X,Y) X-Y
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)
int main(void)
{
int result;
int num_01 = 15, num_02 = 7;
result = SUB(num_01, num_02);
PRT(result);
return 0;
}
실행 결과
계산 결과는 8입니다.
함수와 매크로 함수
매크로 함수는 일반 함수와는 달리 단순 치환만을 해주므로, 일반 함수와 완전히 똑같은 방식으로 동작하지는 않습니다.
다음 예제는 일반 함수와 매크로 함수와의 차이를 보여주는 예제입니다.
예제
#include <stdio.h>
#define SQR(X) X*X
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)
int main(void)
{
int result;
int x = 5;
result = SQR(10);
PRT(result);
result = SQR(x);
PRT(result);
result = SQR(x+3);
PRT(result);
return 0;
}
실행 결과
계산 결과는 100입니다.
계산 결과는 25입니다.
계산 결과는 23입니다.
위의 예제에서 맨 마지막의 매크로 함수는 예상한 결과와는 전혀 다른 결괏값을 반환합니다.
선행처리기는 매크로 정의에서 모든 X를 X+3으로 대체합니다.
따라서 SQR(x+3)은 다음과 같이 대체되어 계산됩니다.
x+3*x+3 = 5+3*5+3 = 5+15+3 = 23
예상한 결괏값은 8*8=64였지만 전혀 다른 결괏값이 반환되는 것입니다.
일반 함수는 인수를 프로그램이 실행 중일 때 전달받지만, 매크로 함수는 인수를 컴파일 이전에 미리 치환하기 때문입니다.
따라서 이와 같은 오류를 미리 방지하기 위해서는 다음 예제의 ①번 코드처럼 각 인수를 모두 괄호(())로 묶어줘야 합니다.
예제
#include <stdio.h>
① #define SQR(X) ((X)*(X)) // 매크로 함수는 이처럼 모든 인수를 괄호로 묶어줘야 함.
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)
int main(void)
{
int result;
int x = 5;
result = SQR(10);
PRT(result);
result = SQR(x);
PRT(result);
result = SQR(x+3);
PRT(result);
return 0;
}
실행 결과
계산 결과는 100입니다.
계산 결과는 25입니다.
계산 결과는 64입니다.
위의 예제는 앞선 예제를 수정하여 정상적인 결과를 반환하게 해주는 예제입니다.
이처럼 매크로 함수가 일반 함수와 같이 동작하기 위해서는 다음과 같은 사항에 주의하여 작성해야 합니다.
1. 매크로 함수의 전체를 괄호(())로 감싸야 합니다.
2. 매크로 함수의 인수들도 각각 괄호로 감싸야 합니다.
3. 매크로 함수를 호출할 때에는 증감 연산자(++, --)나 복합 대입 연산자 등은 사용하지 않는 것이 좋습니다.
매크로 함수의 장단점
매크로 함수의 장점은 다음과 같습니다.
1. 매크로 함수는 단순 치환만을 해주므로, 인수의 타입을 신경 쓰지 않습니다.
2. 매크로 함수를 사용하면 여러 개의 명령문을 동시에 포함할 수 있습니다.
3. 함수 호출에 의한 성능 저하가 일어나지 않으므로, 프로그램의 실행속도가 향상됩니다.
매크로 함수의 단점은 다음과 같습니다.
1. 원하는 결과를 얻는 정확한 매크로 함수의 구현은 어려우며, 따라서 디버깅 또한 매우 어렵습니다.
2. 매크로 함수의 크기가 증가하면 증가할수록 사용되는 괄호 또한 매우 많아져서 가독성이 떨어집니다.
따라서 매크로 함수는 크기가 큰 함수보다는 간단한 함수를 대체하는 데 사용하는 것이 좋습니다.
#과 ## 연산자
#과 ## 연산자는 선행처리기 연산자로 #define 선행처리 지시문에서만 사용되는 연산자입니다.
C언어에서 토큰(token)이란 컴파일러가 인식하는 최소 단위의 문자나 문자열을 의미합니다.
이 두 연산자는 바로 이러한 토큰 단위의 연산에서 사용됩니다.
# 연산자
# 연산자는 매크로 함수의 대체 리스트 안의 인수 앞에 사용하여, 토큰을 문자열로 변환시켜줍니다.
해당 토큰은 실인수로 치환되면서 양쪽에 위치한 큰따옴표("")를 포함해 그대로 문자열 상수로 변환됩니다.
# 연산자를 사용하면 문자열 안에 매크로 함수로 전달된 인수를 포함시킬 수 있습니다.
예제
#include <stdio.h>
#define SQR(X) printf(""#X"의 제곱은 %d입니다.\n", ((X)*(X)))
int main(void)
{
int x = 5;
SQR(x);
SQR(3+4);
return 0;
}
실행 결과
x의 제곱은 25입니다.
3+4의 제곱은 49입니다.
## 연산자
## 연산자는 두 개의 토큰을 하나의 토큰으로 결합해 주는 선행처리기 연산자입니다.
이 연산자는 함수 같은 매크로뿐만 아니라 객체 같은 매크로의 대체 리스트에도 사용할 수 있습니다.
이 연산자를 사용하면 변수나 함수의 이름을 프로그램의 런타임에 정의할 수 있습니다.
다음 예제에서는 XN(n)이라는 매크로 함수를 사용하여 변수의 이름을 동적으로 작성하고 있습니다.
예제
#include <stdio.h>
#define XN(n) x ## n
int main(void)
{
int XN(1) = 10;
int XN(2) = 20;
printf("x1에 저장된 값은 %d입니다.\n", x1);
printf("x2에 저장된 값은 %d입니다.\n", x2);
return 0;
}
실행 결과
x1에 저장된 값은 10입니다.
x2에 저장된 값은 20입니다.
다음 예제는 # 연산자와 ## 연산자를 이용하여, 동적으로 작성한 변수의 이름에 접근하는 예제입니다.
예제
#include <stdio.h>
#define XN(n) x ## n
#define PRT_XN(n) printf("x"#n"에 저장된 값은 %d입니다.\n", x ## n)
int main(void)
{
int XN(1) = 10;
int XN(2) = 20;
PRT_XN(1);
PRT_XN(2);
return 0;
}
실행 결과
x1에 저장된 값은 10입니다.
x2에 저장된 값은 20입니다.