Programming/C

C언어 - 매크로와 선행처리기(preprocessor)

JunsuKim 2021. 10. 27.
728x90

선행처리

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

선행처리는 선행처리기에 의해, 컴파일은 컴파일러에 의해, 링크는 링커에 의해 진행이 된다.

컴파일 과정을 거치면 바이너리 데이터로 이뤄진 오브젝트 파일이 생성된다.

하지만 컴파일 이전에 진행되는 선행처리 과정을 거쳐 생성되는 파일은 그냥 소스파일이다.

선행처리기가 하는 일은 매우 단순한데, 삽입해 놓은 선행처리 명령문대로 소스코드의 일부를 수정한다.

여기서 수정이란, 단순 치환의 형태를 띠는 경우가 대부분이다.

 

선행처리 명령문은 # 키워드로 시작하며, 컴파일러가 아닌 선행처리기에 의해 처리되기 때문에 끝에 세미클론(;)을 붙일 필요가 없다.

#define PI 3.14

위와 같은 명령문이 삽입돼 있는 소스파일을 선행처리의 과정에서 변환되는 것을 보자.

#define PI 3.14

int main() {
    . . . .
    num = PI * 3.5;
    //num = 3.14 * 3.5;
    . . . .
}

 

선행처리에 대해 알아봤으니, 이제 선행처리 명령문들을 알아보자.

#define: Object-like macro

 

선행처리 명령문은 기본적으로 세 부분으로 나뉜다.

먼저 #define을 가리켜 지시자라고 한다.

선행처리기가 이 부분을 보고 프로그래머가 지시하는 바를 파악하기 때문에 지시자라고 한다.

 

#define 지시자는 선행처리기에 "이어서 등장하는 매크로를 매크로 몸체로 치환하라"라고 지시한다.

따라서 위 그림을 보면 "매크로 PI를 매크로 몸체 3.14로 전부 치환하라"는 것이다.

PI라는 이름의 매크로는 이 자체로 상수 3.14가 된 것이다.

PI같은 매크로를 가리켜 오브젝트와 유사한 매크로(Object-like macro) 또는 매크로 상수라 한다.

#define: Function-like macro

매개변수가 존재하는 형태로 매크로를 정의할 수 있다.

이렇게 매개변수가 존재하는 매크로는 동작 방식이 함수와 유사하여 함수와 유사한 매크로(Function-like macro) 또는 매크로 함수라고 한다.

#define SQUARE(x) x * x

매크로 상수와 같이 시작은 #define으로 한다.

그러나 매크로에 괄호가 등장하여 "SQUARE(x)와 같은 패턴이 등장할 시 x * x로 치환하여라"라고 해석된다.

SQUARE(123);

매크로를 정의한 후 이러한 문장이 나왔다면 이는 123 * 123으로 치환된다.

잘못된 매크로 정의

앞에서 SQUARE(x)라는 이름을 가진 매크로를 정의했었다.

만약 SQUARE(3+2)가 있다 하면 5 * 5인 25가 나온다 생각할 수 있다.

하지만 출력 결과는 11이 나올 것이다. 치환이 될 때 3 + 2 * 3 + 2 이처럼 되기 때문이다.

이를 해결하기 위해서는 SQUARE((3+2))와 같이 구성하면 된다.

매크로를 두 줄에 걸쳐 정의

매크로의 길이가 길어지면 가독성이 떨어지기 때문에 두 줄에 걸쳐 매크로를 정의하기도 한다.

하지만 임의로 줄을 변경하게 되면 에러가 생긴다.

이를 해결하기 위해 메크로를 두 줄 이상에 겹쳐 정의할 때에는 (역 슬래쉬)\문자를 활용하여 줄이 바뀌었다는 것을 명시해야 한다.

#define SQUARE(x) \
        ((x) * (x))

앞서 정의된 매크로를 매크로로 정의

먼저 정의된 매크로는 뒤에서 정의할 매크로에 사용이 가능하다.

#include<stdio.h>
#define PI 3.14
#define PRODUCT(X, Y) ((X) * (Y))
#define CIRCLE_AREA(R) (PRODUCT((R), ((R)) * PI)

int main() {
    double rad = 2.1;
    printf("반지름 %g인 원의 넓이: %g\n", rad, CIRCLE_AREA(rad));
    return 0;
}

매크로 함수의 장단점

매크로 함수의 장점

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

일반 함수에 비해 속도가 빠른 이유를 보면,

일반 함수에서는 호출된 상수를 위한 스택 메모리의 할당, 실행 위치의 이동과 매개변수로의 인자 전달, return 문에 의한 값의 반환이 있다.

이 때문에 함수를 빈번히 호출하게 되면 속도의 저하로 이어지게 된다.

하지만 매크로 함수는 선행처리기에 의해 몸체 부분이 매크로 함수의 호출 문장을 대신하여 일반 함수 호출 시의 사항들을 동반하지 않아 속도가 더 빠르다.

 

매크로 함수의 단점

  • 정의하기가 까다롭다.
  • 디버깅하기 어렵다.

아래와 같은 함수가 있다 하자.

int DiffABS(int a, int b) {
    if(a>b) return a - b;
    else return b - a;
}

이를 매크로 함수로 정의하려면 조건 연산자를 떠올려야 한다.

#define DIFF_ABS(X, Y) ((X)>(Y) ? (X)-(Y) : (Y) - (X))

이를 떠올리기도 어려울테고, 괄호를 여러 번 쳐야 하기 때문에 부담스럽게 느껴진다.

 

* 매크로 함수를 정의할 때는 작은 크기의 함수 또는 호출의 빈도수가 높은 함수를 정의하는 것이 좋다.

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

1. #if ... #endif: 참이라면

#include<stdio.h>
#define ADD 1
#define MIN 0

int main() {
	int n1, n2;
	scanf("%d %d", &n1, &n2);

#if ADD 
	printf("%d + %d = %d\n", n1, n2, n1 + n2);
#endif

#if MIN
	printf("%d - %d = %d\n", n1, n2, n1 - n2);
#endif

	return 0;
}

위 코드에서는 매크로 ADD와 MIN을 각각 1과 0의 관계로 ADD가 참이 되어 MIN일 때의 실행은 삭제가 된다.

따라서 출력 결과는 3과 4를 입력했다면 3 + 4 = 7이 나오게 된다.

이러한 매크로를 사용할 때는 상황에 따라 두 매크로의 값을 적절히 변경하면서 사용해야 한다.

 

2. #ifdef ... #endif: 정의되었다면

#if...#endif은 참, 거짓을 기준으로 동작했다면, #ifdef...#endif는 매크로가 정의되었느냐로 동작한다.

#include<stdio.h>
// #define ADD 1
#define MIN 0

int main() {
	int n1, n2;
	scanf("%d %d", &n1, &n2);

#if ADD 
	printf("%d + %d = %d\n", n1, n2, n1 + n2);
#endif

#if MIN
	printf("%d - %d = %d\n", n1, n2, n1 - n2);
#endif

	return 0;
}

 

3. #ifndef ... #endif: 정의되지 않았다면

ifndef의 n은 not을 의미한다. 따라서 매크로가 정의되어 있지 않다면 실행이 된다.

예제는 바로 위의 코드를 보도록 하자.

 

4. #else 삽입: #if, #ifdef, #ifndef에 해당

if문에 else를 추가할 수 있는 것처럼, #if, #ifdef, #ifndef문에도 #else문을 추가할 수 있다.

 

5. elif 삽입: #if에만 해당

if문에 else if를 여러 번 추가하는 것과 같다.

문자열 내에서 매크로의 매개변수 치환

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

이러한 매크로를 정의했을 때, STRING_JOB(홍길동, 의적)을 실행한다면 홍길동의 직업은 의적입니다.라고 뜨지 않고,

A는 B입니다.라고 뜬다.

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

 

이를 해결하기 위해 #연산자를 쓰면 된다.

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

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

##연산자

##연산자는 필요한 형태대로 단순하게 결합시켜주는 연산자이다.

#define CON(UPP, LOW) UPP ## 00 ## LOW
int num = CON(22, 77)

220077

 

728x90

댓글