들어가며
전 시간에는 끔찍한 매크로 코드들의 향연으로 먹은 식사가 살짝 불편하지 않았던가?
오늘은 그런 걱정을 하지 않아도 된다.
왜냐하면 식사를 하지 않고 이번 글을 보길 권장하기 때문이다.
이미 식사를 하고 온 사람들은 가까운 병원에서 위장을 세척한 후 공복 상태에서 이 글을 정독하기 바란다.
매크로를 연속으로 정의하는 방법과 사용
이 글을 보고 있는 독자 여러분은 지금까지 숫자 한 개나 코드 한 줄을 매크로로 정의하여 사용해오지 않았는가?
사실 이 글은 나만 보고 있기 때문에 그렇게 사용해 온 것으로 간주할 것이다.
전적으로 내 마음이니 그저 따라오길 바란다.
각설하고, 그렇다면 여러줄을 매크로로 만든다면 상당히 편할 것으로 예상이 된다.
#define INFO_FATALBLOW (코드 1) \
(코드 2) \
(코드 3)
#define은 줄바꿈이 일어날 때 '\(역 슬래쉬)'를 사용하여 여러 줄을 매크로로 만들 수 있다.
그리고 맨 마지막 코드에서는 역슬래쉬를 사용하지 않아도 된다.
연속 매크로를 사용할 때 주의할 점
연속으로 정의된 매크로를 사용할 때는 조건문과 반복문을 사용할 때 주의해야 할 점이 있다.
우선 내가 적에게 2번의 연속 피해를 입히는 스킬을 사용했지만 상대가 그 스킬을 무효화하였다고 가정하자.
그럼 게임 개발자의 입장에서는 피해를 입혔다는 문구를 출력해서는 안 된다.
#define INFO_FATALBLOW(x) printf("%d의 피해를 입힘.\n", x*(0.7)); \
printf("%d의 피해를 입힙.\n", x*(0.6));
int main()
{
int atk_success = 0; // 대충 공격에 실패했다는 변수
if (atk_success == 1) // 공격에 성공했을 때
INFO_FATALBLOW(100); // 70의 피해를 입혔다고 출력되고
// 60의 피해를 입혔다고 출력되는데
return 0;
}
우선 위와 같은 상황으로 코드를 구성해 보았다. 끔찍하지 않은가?
우린 지금 공격에 실패한 상황이므로 문구가 출력되서는 안 된다.
하지만 실행 결과는 우리의 예상과는 다르다. 위의 코드를 실행시켜보면,
'60의 피해를 입힘.'이라고 문구가 출력된다.
위 코드를 전처리기 과정을 거쳐보면 코드가 아래와 같이 치환되기 때문이다.
int main()
{
int atk_success = 0; // 대충 공격에 실패했다는 변수
if (atk_success == 1) // 공격에 성공했을 때
printf("%d의 피해를 입힘.\n", x*(0.7));
printf("%d의 피해를 입힙.\n", x*(0.6));
return 0;
}
아직 모르겠는가?
int main()
{
int atk_success = 0; // 대충 공격에 실패했다는 변수
if (atk_success == 1) // 공격에 성공했을 때
{
printf("%d의 피해를 입힘.\n", x*(0.7));
printf("%d의 피해를 입힙.\n", x*(0.6));
}
return 0;
}
이 정도면 몸이 고생하는 우리들이라도 알아먹을 것이다.
무려 중괄호를 사용하여 출력되지 말아야 할 문구가 출력되는 현상을 고친 것이다!
그럼 우리는 전처리기 과정 전의 매크로 함수를 원하는 결과가 도출될 수 있도록 수정할 수 있다.
#define INFO_FATALBLOW(x) printf("%d의 피해를 입힘.\n", x*(0.7)); \
printf("%d의 피해를 입힙.\n", x*(0.6));
int main()
{
int atk_success = 0; // 대충 공격에 실패했다는 변수
if (atk_success == 1) // 공격에 성공했을 때만
{
INFO_FATALBLOW(100); // 70의 피해를 입혔다고 출력되고
} // 60의 피해를 입혔다고 출력된다!
return 0;
}
아! 이제 좀 편한 것 같다. 생각해 보니 오늘 코드는 속이 좀 괜찮은 것 같다.
아닌가? 아니라고 생각하는가? 괜찮다. 어차피 독자는 나만 있기 때문에 내가 괜찮으면 괜찮은 것이다.
어쨌든 if문뿐만 아니라 for, while 등의 반복문 또한 동일한 맥락으로 중괄호에 유의해야 한다.
매크로와 연산자의 우선순위
매크로를 정의할 때는 연산자와의 우선순위를 주의해야 한다.
먼저 두 수를 곱하는 매크로와 더하는 매크로를 정의해보도록 하자.
#include <stdio.h>
#define MUL(a, b) a * b
#define ADD(a, b) a + b
int main()
{
printf("%d\n", MUL(10, 20));
printf("%d\n", MUL(1 + 2, 3 + 4));
printf("%d\n", ADD(1, 2));
printf("%d\n", ADD(1, 2) * 3);
return 0;
}
위처럼 코드를 구성했다고 하자.
"아 ㅋㅋ 그럼 각각 200, 21, 3, 9가 출력되것네?"
그렇다. 모두의 예상을 깨버리고 출력된 값은 '200, 11, 3, 7'이다.
이게 어떻게 된 일인가. 궁금하지 않은가? 궁금하다! 내가!
printf("%d\n", MUL(10, 20)); // 200: 10 * 20
printf("%d\n", MUL(1 + 2, 3 + 4)); // 11: 1 + 6 + 4 = 1 + (2 * 3) + 4
printf("%d\n", ADD(1, 2)); // 3: 1 + 2
printf("%d\n", ADD(1, 2) * 3); // 7: 1 + 6 = 1 + (2 * 3)
위 코드들 중 2행과 5행을 유심히 관찰해보자.
printf("%d\n", MUL(1 + 2, 3 + 4)); // 11: 1 + 6 + 4 = 1 + (2 * 3) + 4
이 코드가 전처리기 과정을 거치면
printf("%d\n", 1 + 2 * 3 + 4); // 11: 1 + 6 + 4 = 1 + (2 * 3) + 4
위 코드와 같이 치환되어 버리기 때문이다!
따라서 연산자 우선순위에 따라서 곱의 연산 이후 합의 연산을 거쳐 11의 값이 도출되는 것이다.
printf("%d\n", ADD(1, 2) * 3); // 7: 1 + 6 = 1 + (2 * 3)
이 코드 또한 마찬가지이다.
이 코드도 전처리기 과정을 거치면
printf("%d\n", 1 + 2 * 3); // 7: 1 + 6 = 1 + (2 * 3)
위와 같이 치환되므로 7의 값이 도출되는 것이다.
자 그렇다면 이런 난감한 경우를 어떻게 하면 고칠 수 있을까!
글의 윗부분에서 몸이 고생하는 우리는 문구가 출력되는 면 되는 경우를 접한 적이 있다.
감이 오는가?
#include <stdio.h>
#define MUL(a, b) ((a) * (b)) // a와 b, 결과를 모두 괄호로 묶어줌
#define ADD(a, b) ((a) + (b)) // a와 b, 결과를 모두 괄호로 묶어줌
int main()
{
printf("%d\n", MUL(10, 20)); // 200: 10 * 20
printf("%d\n", MUL(1 + 2, 3 + 4)); // 21: 3 * 7
printf("%d\n", ADD(1, 2)); // 3: 1 + 2
printf("%d\n", ADD(1, 2) * 3); // 9: 3 * 3
return 0;
}
이 정도면 감이 올 것이다.
무려 소괄호를 사용하여 원하지 않는 연산과정을 올바르게 한 것이다!
이렇게 연산자 우선순위에 따라서 의도치 않은 연산 결과가 도출되는 것을 해결하기 위해서는,
매크로의 인수와 결과를 모두 '소괄호'로 묶어주면 되는 것이다!
위 코드를 전처리기 과정을 거친 후의 코드는 다음과 같다.
#include <stdio.h>
#define MUL(a, b) ((a) * (b)) // a와 b, 결과를 모두 괄호로 묶어줌
#define ADD(a, b) ((a) + (b)) // a와 b, 결과를 모두 괄호로 묶어줌
int main()
{
printf("%d\n", (10)*(20)); // 200: 10 * 20
printf("%d\n", (1+2)*(3+4)); // 21: 3 * 7
printf("%d\n", (1)+(2)); // 3: 1 + 2
printf("%d\n", (1+2)*3); // 9: 3 * 3
return 0;
}
매크로 연결의 사용
자, 마지막으로 '매크로 연결'에 대하여 알아보자.
#define에서 '##'을 사용하면 여러 코드 혹은 값을 이어 붙일 수 있다.
- #define (매크로 이름)(a, b) a##b
#include <stdio.h>
#define CONCAT(a, b) a##b // a와 b를 붙이는 CONCAT 매크로 정의
int main()
{
printf("%d\n", CONCAT(1, 2)); // 12
return 0;
}
일단 이 개념을 처음 배우는 사람으로서 무슨 일이 일어날지 감이 잘 잡히지 않는다.
아무튼 잡히지 않는 것이다.
신기하게도, 실행 결과는 '12'가 출력된다.
이게 어떻게 된 일인가!
#define CONCAT(a, b) a##b // a와 b를 붙이는 CONCAT 매크로 정의
예상외로 그 원인은 아주 간단하며, 바로 이것에 있다.
위에서 '##'은 두 인자(코드 혹은 값)를 '붙이는(이어주는)' 역할을 한다고 했다.
따라서 1과 2를 이어 붙인 '12'가 출력되는 것이다.
매크로 연결의 응용
자, 그럼 진짜 마지막으로 매크로 연결을 응용해보도록 하자.
솔직히 배운 것이 있다면 그것을 응용과 활용을 해보지 않는가?
잘 모르겠는가? 그럼 중등, 고등 수학을 떠올려 보자.
어떤 개념을 배우고 우린 항상 그걸 실생활에 적용시켜 응용하지 않았던가?
대표적으로는 미적분, 기하와 벡터 등등이 있을 것이다.
수험생활의 PTSD가 떠오르니 이만 각설하겠다.
매크로 연결의 진짜 응용
##을 좀 더 응용한다면 다음과 같이 매크로를 이용하여 함수를 호출할 수도 있다.
#include <stdio.h>
#define EXECUTER(x) hello##x() // hello와 x를 붙여서 호출하는 EXECUTER 매크로 정의
void hello1()
{
printf("hello 1\n");
}
void hello2()
{
printf("hello 2\n");
}
int main()
{
EXECUTER(1); // hello1 함수 호출
EXECUTER(2); // hello2 함수 호출
return 0;
}
뭔가 감이 잘 오지 않는가?
그렇다. 우린 아직 몸이 고생하는 단계이기 때문에 충분히 그럴 수 있다 생각한다.
그럼 전처리기 과정을 거친 다음 코드를 보도록 하라.
#include <stdio.h>
#define EXECUTER(x) hello##x() // hello와 x를 붙여서 호출하는 EXECUTER 매크로 정의
void hello1()
{
printf("hello 1\n");
}
void hello2()
{
printf("hello 2\n");
}
int main()
{
hello1() // hello1 함수 호출
hello2() // hello2 함수 호출
return 0;
}
이해가 가는가?
간단하게, EXECUTER이라는 매크로 함수를 정의하였고 x(전달 인자) 뒤에 괄호를 덧붙여 함수를 호출함을 나타냈다.
이후 전달 인자에 어떤 인자를 전달하느냐에 따라서 'hello##x()'가 'hello1()' 혹은 'hello2()'로 결정되는 것이다.
마치며
오늘은 매크로에 관한 두 번째 시간이었다.
1. 매크로의 연속 정의
2. 매크로의 연산자 우선순위
3. 매크로 연결
이 세 가지를 배워 보았다.
어떤가. 머릿속에서 그 어떤 것도 남아있지 않는가?
축하한다! 그렇다면 지금 스크롤을 맨 위에서부터 다시 이곳까지 천천히 정독하여 내려오길 바란다!
그럼에도 머릿속에 아무 것도 남아있지 않다면,
축하한다! 몸이 좀더 고생하여 게으른 프로그래머가 될 수 있는 가능성이 있는 지망생이다!
아무튼 메크로와 관련하여 여기까지 알아보도록하자.
참고 및 출처
-
코딩 도장