공부/윤성우의 열혈 C 프로그래밍

윤성우의 열혈 C 프로그래밍 Chapter26 매크로와 선행처리기

셩잇님 2022. 11. 24. 10:53
반응형

선행처리기와 매크로

선행처리는 컴파일 이전의 처리를 의미

선행처리가 하는 일은 지극히 단순하다. 우리가 삽입해 놓은 선행처리 명령문대로 소스코드의 일부를 수정할 뿐인데 여기서 말하는 수정이란 단순 치환의 형태를 띠는 경우가 대부분이다.

예) #define PI 3.14

이처럼 선행처리 명령문은 # 문자로 시작하며, 컴파일러가 아닌 선행처리기에 의해 처리되는 문장이기 때문에 명령문의 끝에 세미콜론을 붙이지 않는다. 그리고 이렇게 이렇게 구성된 명령문은 성행처리기에 다음과 같은 메시지를 전달한다.

 

PI를 만나면 3.14로 치환하여라.

참고로 여기서 말하는 선행처리란, 컴파일 이전의 처리를 의미한다.

따라서 소스파일은 컴파일러에 의허 컴파일 되기 이전에 선행처리기에 의해 선행처리의 과정을 거치게 된다고 이야기한다.

 

대표적인 선행처리 명령문

#define : Object-like macro

예시로 든 #define PI 3.14를 다시 관찰하자.
제일 먼저 등장하는 #define을 지시자라고 하며 이는 선행처리기에게 다음과 같은 내용을 지시한다.

 

이어서 등장하는 매크로를 마지막에 등장하는 매크로 몸체로 치환하라!

위 예시에서 보듯 #define 지시자 뒤에 등장하는 것을 가르켜 매크로라고 하고 그 뒤에 등장하는 것을 가리켜 매크로 몸체라 한다. 따라서 위 명령문은 다음의 내용을 선행처리기에게 지시한다.

 

매크로 PI를 매크로 몸체 3.14로 전부 치환하라.

 

결과적으로 PI라는 이름의 매크로는 그 자체로 상수 3.14가 된 셈이다. 참고로 PI와 같은 매크로를 가르켜 오브젝트와 유사한 매크로 또는, 간단히 매크로 상수라 한다.

 

#define : Function-like macro

매크로는 매개변수가 존재하는 형태로도 정의할 수 있다. 그리고 이 매개변수가 존재하는 매크로는 그 동작방식이 마치 함수와 유사해 매크로 함수로 부르기도 한다.

예) #define SQUARE(X) X*X

위 예시는 SQUARE(X) 를 만날 시 X*X유형으로 바꿔라. 즉 치환을 시킨다.

예를 들어 위 매크로 정의 이후 다음과 같은 문장을 접했다고 가정하자

SQUARE(123);
SQUARE(NUM);

이는 선행 처리 후 다음과 같이 변경된다.

123 * 123;
NUM * NUM;

이러한 변환의 결과가 마치 함수의 호출과 유사하여 선행처리기에 의해 변환되는 과정 자체를 가리켜 매크로 확장이라고 한다.

 

잘못된 매크로 정의

SQUARE(3+2)

이를 함수의 관점으로 보면 3과 2의 합인 SQUARE 함수의 인자로 전달하는것으로 생각하는 것이 당연하다. 즉 25가 반환되어야 한다. 그러나 출력 결과는 11이 되어 나온다. 이러한 문제는 먼저 연산을 하고 연산 결과를 가지고 함수를 호출하게끔 돕는 것은 컴파일러지 선행처리기가 아니다. 그런데 매크로는 선행처리기에 의해 처리 된다. 때문에 위의 문장은 단순히 다음과 같이 치환된다.

3+2*3.2

따라서 그 결과로 11이 출력된다. 그렇다면 해결책은 무엇인가?

SQUARE((3+2))로 해결하면 된다.

 

매크로를 두 줄에 걸쳐서 정의하려면?

정의하는 매크로가 길어질 경우 가독성을 높이기 위해 두 줄에 걸쳐 매크로를 정의한다. 그런데 임의로 줄을 변경하면 에러가 발생하므로 매크로를 두 줄 이상 결처서 정의할때는 만드시 \ 문자를 활용해 줄이 바뀜을 명시해야 한다.

 

if, endif : 참이라면
ifdef, endif : 정의되었다면
ifndef, endif : 정의되지않았다면
else의 삽입 : #if, #ifdef, #ifndef에 해당
elif의 삽입 : #if에만 해당

 

 

매크로 함수의 장점

  1. 매크로 함수는 일반 함수에 비해 실행속도가 빠르다.
  2. 자료형에 따라 별도의 함수를 정의하지 않아도 된다.

 

매크로 함수의 단점

  1. 정의하기가 까다롭다
  2. 디버깅하기가 쉽지 않다.

 

매크로 함수로 정의해야 하는 유형

  1. 작은 크기의 함수
  2. 호출의 빈도수가 높은 함수.

 

조건부 컴파일을 위한 매크로

if, endif : 참이라면
ifdef, endif : 정의되었다면
ifndef, endif : 정의되지않았다면
else의 삽입 : #if, #ifdef, #ifndef에 해당
elif의 삽입 : #if에만 해당

 

매개변수의 결합과 문자열화

문자열 내에서는 매크로의 매개변수 치환이 발생하지 않습니다.

문자열 구성을 위해 매크로 함수를 다음의 형태로 정의하였다.

#define STRING_JOB(A, B) "A의 직업은 B입니다."

그리고 STRING_JOB의 (이동춘, 나무꾼) 이라는 매크로 문장이 다음의 문자열을 만들어 낼 것을 기대하였다.

 

"이동춘의 직업은 나무꾼입니다."

하지만 다음과 같은 이유로 선행처리기는 우리의 기대대로 문자열을 만들어내지 못한다.

"문자열 안에서는 매크로의 매개변수 치환이 발생하지 않습니다."

따라서 다음 두 매크로 문장은

 STRING_JOB (이동춘, 나무꾼)
 STRING_JOB (한상순, 사냥꾼)

둘 다 "A의 직업은 B입니다."로 치환이 된다. 즉 A와 B는 매크로의 매개변수 치환이 이루어지지 않는다. 이를 어떻게 해결해야 할까?

 

문자열 내에서 매크로의 매개변수 치환이 발생하게 만들기 : #연산자

#define STR(ABC) #ABC

위 문장에는 다음의 뜻이 담겨있다

매개변수 ABC에 전달되는 인자를 문자열 "ABC"로 치환해라!

이렇듯 #연산자는 치환의 결과를 문자열로 구성하는 연산자이다.

 

필요한 형태대로 단순하게 결합하기 : ##연산자

이 연산자는 매크로 함수의 전달인자를 다른 대상과 이어줄 때 사용한다. 그럼 이와 관련해서 문장을 보자.

예) #define CON(UPP, LOW) UPP ## 00 ## LOW

위의 매크로 몸체에는 UPP와 00 그리고 LOW가 순서대로 이어질 수 있도록 ## 연산자가 사용되었다. 따라서 다음과 같은 문장을 삽입하면,

int num = CON(22, 77);

이는 선행처리기에 의해 아래와 같이 변환된다.

int num = 220077;
 

 

반응형