67. 매크로(Macro)
1. 키워드
- 매크로(Macro)
- C 전처리기(C Preprocessor)
2. 매크로 사용하기
- C에서는 컴파일러에 특정 작업을 지시하는 전처리기를 제공한다.
- 보통 전처리기는 반복되는 값이나 작업을 미리 정의할 때 사용하며 컴파일 옵션 설정이나 조건부 컴파일도 가능하다.
- 이번에는 전처리기에서
#define
으로 매크로를 정의하는 방법을 알아보자.
3. 매크로 정의하기
- 배열이나 동적 메모리를 할당한 포인터는 반복문과 함께 사용하는 경우가 많다.
- 보통 배열, 메모리 크기만큼 반복하면서 값을 출력하거나 저장한다.
- 간단하게 배열을 선언한 뒤 반복문으로 문자를 하나씩 저장하고 출력해 보자.
#include <stdio.h>
int main()
{
char s1[10]; // 문자 10개 크기의 배열 선언
for (int i = 0; i < 10; i++) // 배열 크기만큼 반복
{
s1[i] = 97 + i;
}
for (int i = 0; i < 10; i++) // 배열 크기만큼 반복
{
printf("%c ", s1[i]);
}
return 0;
}
// a b c d e f g h i j
- 이 코드의 문제점은
10
이3
번 반복된다는 점이다. - 만약 배열의 크기를 바꾸면 반복문에 있는
10
도 전부 바꿔야 한다. - 특히,
10
은 배열s1
의 크기를 뜻하지만, 코드에 내용이 더 추가되어서 배열의 크기와는 별개로10
을 사용할 수도 있다. - 즉, 의미가 서로 다른
10
이 섞여서 사용되면 각각의 의미가 헷갈리기 쉽다.
- 이런 문제를 해결할 때는 매크로를 정의하면 된다.
#define
지시자는 값을 다른 이름으로 정의하며 이름을 짓는 방법은 변수와 같다.- 보통 매크로는 대문자를 사용한다.
#include <stdio.h>
#define ARRAY_SIZE 10 // 10을 ARRAY_SIZE로 정의
int main()
{
char s1[ARRAY_SIZE]; // 10 대신 ARRAY_SIZE 매크로 사용
for (int i = 0; i < ARRAY_SIZE; i++) // 10 대신 ARRAY_SIZE 매크로 사용
{
s1[i] = 97 + i;
}
for (int i = 0; i < ARRAY_SIZE; i++) // 10 대신 ARRAY_SIZE 매크로 사용
{
printf("%c ", s1[i]);
}
return 0;
}
// a b c d e f g h i j
- 먼저
#define
을 사용하여 값10
을ARRAY_SIZE
로 정의했다.
- 이제
10
을 써야 할 자리에 앞에서 정의한ARRAY_SIZE
매크로를 사용했다.
char s1[ARRAY_SIZE]; // 10 대신 ARRAY_SIZE 매크로 사용
for (int i = 0; i < ARRAY_SIZE; i++) // 10 대신 ARRAY_SIZE 매크로 사용
{
s1[i] = 97 + i;
}
for (int i = 0; i < ARRAY_SIZE; i++) // 10 대신 ARRAY_SIZE 매크로 사용
{
printf("%c ", s1[i]);
}
#define
은 컴파일 직전에 처리되므로 전처리기 과정을 거치면ARRAY_SIZE
는10
으로 바뀌게 된다.- 바뀐 코드는 전처리기 과정 때 임시로 생성될 뿐 원본 코드에 영향을 주지 않는다.
- 이처럼 연관되는 값이 계속 사용될 때는 값을 일일이 입력하기 보다는 매크로를 정의하여 사용하는 것이 좋다.
- 만약 배열
s1
크기를 변경하고 싶다면#define
부분의10
을 수정하면ARRAY_SIZE
매크로가 사용된 부분에 일괄 적용된다.
매크로를 매크로로 정의하기
#define
으로 정의한 매크로는 다른 이름으로 다시 정의할 수 있다.
#define ARRAY_SIZE 10 // 10을 ARRAY_SIZE로 정의
#define DEFAULT_ARRAY_SIZE ARRAY_SIZE // ARRAY_SIZE를 DEFAULT_ARRAY_SIZE로 정의
- 이렇게 정의하면 결과적으로
DEFAULT_ARRAY_SIZE
는10
이다.
#define
으로 정의한 매크로를 해제하고 싶을 때는#undef
를 사용한다.
#include <stdio.h>
#define COUNT 10 // 10을 COUNT로 정의
int main()
{
printf("%d\n", COUNT);
#undef COUNT // 앞에서 정의한 COUNT 해제
#define COUNT 20 // 20을 COUNT로 정의
printf("%d\n", COUNT); // #undef로 COUNT를 해제한 뒤 20을 COUNT로 정의했으므로 20이 출력됨
return 0;
}
// 10
// 20
#define
으로 정의한 매크로를 해제(삭제)하고 싶다면#undef
에 매크로 이름을 지정해 주면 된다.- 만약
#undef
로 해제된 매크로를 계속 사용하면 컴파일 에러가 발생한다. - 여기서는
#undef
를 사용하여COUNT
를 해제한 뒤 다시#define
을 사용하여20
을COUNT
로 정의했다.
4. 함수 모양의 매크로 정의하기
#define
은 함수 모양의 매크로도 정의할 수 있다.
#include <stdio.h>
#define PRINT_NUM(x) printf("%d\n", x) // printf("%d\n", x)를 PRINT_NUM(x)로 정의
int main()
{
PRINT_NUM(10); // printf("%d\n", 10)
PRINT_NUM(20); // printf("%d\n", 20)
return 0;
}
// 10
// 20
#define
으로 함수 모양의 매크로를 정의할 때는()
(괄호)안에 자료형은 생략하고 인수의 이름만 지정한다.- 그리고 호출할 함수를 작성한 뒤 매크로에 지정했던 인수를 그대로 함수 안에 넣으면 된다.
- 전처리기 과정을 거치면
PRINT_NUM(x)
는 다음과 같이printf("%d\n", 10)
,printf("%d\n", 20)
으로 바뀌게 된다.
함수를 사용하지 못 하도록 만들려면?
#define
을 사용하면 함수가 아무 동작도 하지 않도록 만들 수 있다.- 다음과 같이 함수 이름으로 된 빈 매크로를 지정하면 이후에 해당 이름으로 함수를 호출해도 사용할 수 없게 된다.
5. 여러 줄을 묶어서 매크로로 정의하기
- 지금까지 숫자 한 개나 코드 한 줄을 매크로로 정의해서 사용했다.
#define
은 줄바꿈이 일어날 때\
를 사용하여 여러 줄을 매크로로 만들 수 있다.- 단, 맨 마지막 줄은
\
를 사용하지 않아도 된다.
#include <stdio.h>
// printf 세 줄을 PRINT_NUM3으로 정의
#define PRINT_NUM3(x) \
printf("%d\n", x); \
printf("%d\n", x + 1); \
printf("%d\n", x + 2);
int main()
{
PRINT_NUM3(10);
PRINT_NUM3(20);
return 0;
}
// 10
// 11
// 12
// 20
// 21
// 22
printf
세 줄을 묶어서PRINT_NUM3
으로 정의했다.- 여기서는 두 번째
printf
에는1
을 더했고, 세 번째printf
에는2
를 더했다.
// printf 세 줄을 PRINT_NUM3으로 정의
#define PRINT_NUM3(x) \
printf("%d\n", x); \
printf("%d\n", x + 1); \
printf("%d\n", x + 2);
- 전처리기 과정을 거치면
PRINT_NUM3(x)
는 다음과 같이printf
세 줄로 바뀌게 된다.
- 여러 줄로 된 매크로를 사용할 때는 조건문, 반복문 사용에 주의해야 한다.
#include <stdio.h>
#define PRINT_NUM3(x) \
printf("%d\n", x); \
printf("%d\n", x + 1); \
printf("%d\n", x + 2);
int main()
{
int num1 = 1;
if (num1 == 2)
PRINT_NUM3(10);
return 0;
}
// 11
// 12
- 소스 코드를 실행했을 때
num1
은1
이므로 다음if
조건문은 실행되지 않아야 하지만11
,12
가 출력되었다.
- 이 코드가 전처리기 과정을 거치면 다음과 같이
if
아래 줄에printf
세 줄이 생긴다. - 자세히 보면 첫 번째 줄
printf
만if
의 영향을 받고 나머지 두 줄은 영향을 받지 않는다는 것을 알 수 있다.
int num1 = 1;
if (num1 == 2)
printf("%d\n", 10); // ← 첫 째줄만 if의 영향을 받음
printf("%d\n", 10 + 1); // ← 두 번째 세 번째 줄은 if의 조건과는 상관없이 무조건 실행됨
printf("%d\n", 10 + 2); // ← 두 번째 세 번째 줄은 if의 조건과는 상관없이 무조건 실행됨
- 따라서
if
조건문에 여러 줄로 된 매크로를 사용할 때는 반드시{}
(중괄호)로 묶어줘야 한다.
if
조건문뿐만 아니라for
,while
반복문도 여러 줄로 된 매크로를 사용할 때는 반드시{}
로 묶어줘야 한다.
6. 스왑 매크로 정의하기
- 이번에는 두 변수의 값을 바꾸는 매크로를 정의하는 방법이다.
- 매크로를 정의하기 전에 먼저 함수로 만들어보자.
- 이 함수의 단점은 바꿀 수 있는 변수의 자료형이
int
타입으로 고정되어 있다는 점이다. - 매개변수를
int
타입 포인터 대신void
타입 포인터로 지정하고, 자료형의 종류를 열거형 등으로 받아도 되지만 코드가 길어진다.
#define
으로 매크로를 만들 때do while
을 사용하면 모든 자료형을 만족하면서 두 변수의 값을 바꿀 수 있다.
#include <stdio.h>
// 매크로 안에서 변수를 선언할 수 있도록 do while 사용
// a와 b의 값을 서로 바꿈
#define SWAP(a, b, type) \
do \
{ \
type temp; \
temp = a; \
a = b; \
b = temp; \
} while (0)
int main()
{
int num1 = 10;
int num2 = 20;
SWAP(num1, num2, int); // 값을 바꿀 자료형으로 int 타입을 지정
printf("%d %d\n", num1, num2); // 두 변수의 값이 바뀜
float num3 = 1.5f;
float num4 = 3.8f;
SWAP(num3, num4, float); // 값을 바꿀 자료형으로 float 타입을 지정
printf("%f %f\n", num3, num4); // 두 변수의 값이 바뀜
return 0;
}
// 20 10
// 3.800000 1.500000
#define
으로SWAP
을 정의할 때 바꿀 변수가 들어갈a
와b
그리고 자료형이 들어갈type
을 지정해 준다.- 함수에서는 자료형을 매개변수에 그대로 전달할 수 없지만, 매크로는 전달하는 것이 아니라 지정한 코드 자체가 바뀌게 되므로 자료형도 지정할 수 있다.
// 매크로 안에서 변수를 선언할 수 있도록 do while 사용
// a와 b의 값을 서로 바꿈
#define SWAP(a, b, type) \
do \
{ \
type temp; \
temp = a; \
a = b; \
b = temp; \
} while (0)
- 다음은
SWAP(num1, num2, int);
와 같이int
타입 변수num1
,num2
와 자료형int
타입을 지정한 뒤 전처리기를 거쳤을 때 바뀐 코드를 나타낸다. - 즉,
type
부분이 자료형int
타입으로 바뀌면서temp
변수를 선언하게 된다. a
와b
도 지정된 변수로 바뀌므로 자료형은 따지지 않아도 된다.
do {
// ↓ type 부분이 자료형으로 바뀌므로 지정한 자료형으로 변수를 선언할 수 있음
int temp;
temp = num1;
num1 = num2;
num2 = temp;
} while (0);
- 여기서 눈 여겨볼 부분은
do while (0)
이다. - 별 의미없는 코드 같지만 실제로는 중요한 역할을 담당한다.
do while (0)
은{}
로 묶여있기 때문에 안에서 변수를 마음대로 선언할 수 있고, 선언된 변수는do while (0)
을 벗어나면 변수가 사라진다.- 즉,
SWAP
매크로를 계속 사용했을 때 같은 이름으로 된 변수가 여러 개 생기지만 컴파일 에러를 방지할 수 있다.
- 다음은
SWAP(num1, num2, int);
와SWAP(num3, num4, float);
이 전처리기를 거쳤을 때 바뀐 코드를 나타낸다.
do {
// ↓ 이름이 같은 변수가 두 개 선언되지만 do while (0) 안이므로 컴파일 에러가 발생하지 않음
int temp;
temp = num1;
num1 = num2;
num2 = temp;
} while (0);
do {
// ↓ 이름이 같은 변수가 두 개 선언되지만 do while (0) 안이므로 컴파일 에러가 발생하지 않음
float temp;
temp = num3;
num3 = num4;
num4 = temp;
} while (0);
- 즉, 변수
temp
가 두 개 선언되지만do while (0)
안이므로 컴파일 에러가 발생하지 않는다. - 만약
SWAP
정의에서do while (0)
과{}
를 삭제하고 컴파일하면temp
가 재정의되었다는 컴파일 에러가 발생하게 된다.
- 특히 매크로에서
do while (0)
을 사용하면if
조건문과for
,while
반복문에서도 문제없이 사용할 수 있다.
- 이 코드가 전처리기를 거치면 다음과 같이 된다.
if (num1 == 10)
do {
int temp;
temp = num1;
num1 = num2;
num2 = temp;
} while(0);
else
printf("10이 아님\n");
- 만약
SWAP
매크로를do while (0)
대신{}
로만 정의하면 전처리기를 거쳤을 때 잘못된 코드가 나온다.
// 중괄호로만 매크로 정의
#define SWAP(a, b, type) { \
type temp; \
temp = a; \
a = b; \
b = temp; \
}
if (num1 == 10)
SWAP(num1, num2, int);
else
printf("10이 아님\n");
- 이 코드가 전처리기를 거치면 다음과 같이
}
(닫는 중괄호) 뒤에;
(세미콜론)이 붙어서 컴파일 에러가 발생한다.
if (num1 == 10)
{
int temp;
temp = num1;
num1 = num2;
num2 = temp;
}; // 세미콜론 때문에 컴파일 에러가 발생
else
printf("10이 아님\n");
7. 매크로와 연산자 우선순위 알아보기
- 매크로를 정의할 때는 연산자 우선순위를 주의해야 한다.
- 먼저 두 수를 곱하는 매크로와 더하는 매크로를 정의해 보자.
#include <stdio.h>
// a와 b를 곱함
#define MUL(a, b) a * b
// a와 b를 더함
#define ADD(a, b) a + b
int main()
{
printf("%d\n", MUL(10, 20)); // 10 * 20
printf("%d\n", MUL(1 + 2, 3 + 4)); // 1 + 6 + 4, 2 * 3이 먼저 계산됨
printf("%d\n", ADD(1, 2)); // 1 + 2
printf("%d\n", ADD(1, 2) * 3); // 1 + 6, 2 * 3이 먼저 계산됨
return 0;
}
// 200
// 11
// 3
// 7
MUL
은a
와b
를 곱하고,ADD
는a
와b
를 더하도록 정의되어 있다.
- 이제 매크로를 사용해 보면
MUL(10, 20)
은200
이 잘 나오지만MUL(1 + 2, 3 + 4)
는3 * 7
의 결과인21
이 나오지 않고11
이 나왔다. - 마찬가지로
ADD(1, 2)
는 결과가 잘 나오지만ADD(1, 2) * 3
은9
가 나와야 하는데7
이 나왔다.
printf("%d\n", MUL(10, 20)); // 10 * 20
printf("%d\n", MUL(1 + 2, 3 + 4)); // 1 + 6 + 4, 2 * 3이 먼저 계산됨
printf("%d\n", ADD(1, 2)); // 1 + 2
printf("%d\n", ADD(1, 2) * 3); // 1 + 6, 2 * 3이 먼저 계산됨
- 왜냐하면
MUL
이 전처리기를 거쳤을 때 다음과 같은 모양이 되므로 연산자 우선순위에 따라2 * 3
이 먼저 계산된다. - 즉,
1 + 6 + 4
라서11
이 나오게 된다.
- 마찬가지로
ADD
도 전처리기를 거치면 숫자와 연산자가 그냥 나열된다. - 여기서도 연산자 우선순위에 따라 덧셈보다 곱셈이 먼저 계산되므로
2 * 3
이 먼저 계산된다. 1 + 6
이라7
이 된다.
- 이렇게 연산자 우선순위에 따라서 의도치 않게 계산 결과가 바뀌는 문제를 해결하려면 매크로의 인수와 결과를 모두
()
로 묶어주면 된다.
#include <stdio.h>
// a와 b, 결과를 모두 괄호로 묶어줌
#define MUL(a, b) ((a) * (b))
// a와 b, 결과를 모두 괄호로 묶어줌
#define ADD(a, b) ((a) + (b))
int main()
{
printf("%d\n", MUL(10, 20)); // 10 * 20
printf("%d\n", MUL(1 + 2, 3 + 4)); // 3 * 7
printf("%d\n", ADD(1, 2)); // 1 + 2
printf("%d\n", ADD(1, 2) * 3); // 3 * 3
return 0;
}
// 200
// 21
// 3
// 9
MUL
과ADD
를 정의할 때a
와b
, 결과를 모두()
로 묶어줬다.
// a와 b, 결과를 모두 괄호로 묶어줌
#define MUL(a, b) ((a) * (b))
// a와 b, 결과를 모두 괄호로 묶어줌
#define ADD(a, b) ((a) + (b))
- 이제
MUL(1 + 2, 3 + 4)
는 전처리기를 거쳐도((1 + 2) * (3 + 4))
와 같은 모양이 되므로 연산자 우선순위에 영향을 받지 않고 의도대로 계산이 된다. - 마찬가지로
ADD(1, 2) * 3
도((1) + (2)) * 3
이 되어 덧셈이 먼저 계산된다.
8. 매크로 연결 사용하기
#define
에서##
을 사용하면 여러 코드(값)을 붙일 수 있다.
#include <stdio.h>
// a와 b를 붙이는 CONCAT 매크로 정의
#define CONCAT(a, b) a##b
int main()
{
printf("%d\n", CONCAT(1, 2));
return 0;
}
// 12
- 먼저 다음과 같이
##
을 사용하여a
와b
를 붙이는CONCAT
매크로를 정의한다.
printf
로CONCAT(1, 2)
를 출력해 보면12
가 나온다.- 즉,
1
과2
를 더해서3
이 나오는 것이 아니라1
과2
를 그대로 붙여서12
가 된다.
##
을 좀 더 응용하면 다음과 같이 매크로로 함수를 호출할 수도 있다.
#include <stdio.h>
// hello와 x를 붙여서 호출하는 EXECUTOR 매크로 정의
#define EXECUTOR(x) hello##x()
void hello1()
{
printf("hello 1\n");
}
void hello2()
{
printf("hello 2\n");
}
int main()
{
EXECUTOR(1); // hello1 함수 호출
EXECUTOR(2); // hello2 함수 호출
return 0;
}
// hello 1
// hello 2
- 먼저 다음과 같이
hello
와x
를 붙이는EXECUTOR
매크로를 정의했다. - 이때
x
뒤에()
를 붙여 함수 호출이라는 것을 나타내준다.
EXECUTOR
에1
을 넣으면hello
와1
이 붙게 되므로hello1
함수를 호출하고,2
를 넣으면hello2
함수를 호출한다.