본문 바로가기
휴지통/C 언어

매크로와 선행처리기(Preprocessor)

by 신재권 2021. 2. 12.

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

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

컴파일 이전에 선행처리의 과정을 거친다는 점에 주목해야 한다.

 

선행처리기가 하는 일은 단순한다. 우리가 삽입한 선행처리명령문대로 소스코드의 일부를 수정한다. 여기서 말하는 수정이란 단순 치환(substitution)의 형태를 띠는 경우가 대부분이다.

#define PI 3.14

위의 문장은 

PI를 3.14로 치환해라 라는 뜻이다 .

 

즉 선행처리 명령문은 # 문자로 시작을 한다.

컴파일러가 아닌 선행처리기에 의해서 처리되는 문장이고, 명령문의 뒤에 세미콜론을 붙이지 않는다.

 


#define : Object- like - macro

 

#define  지시자 

PI 매크로

3.1415 매크로 몸체 

 

#define PI 3.1415

 

선행처리 명령문은 기본적으로 세부분으로 나뉜다. #define을 가리켜 지시자라 한다. 

즉 매크로 PI를 매크로 몸체 3.1415로 전부 치환하라는 뜻이다.

즉 PI와 같은 매크로를 가리켜 오브젝와 유사한 매크로 (object -like macro) 또는 그냥 간단히 '매크로 상수'라한다.

 

#inlcude <stdio.h>

#define NAME "홍길동"
#define AGE 24
#define PRINT_ADDR putsS("주소 : 경기도 용인시 \n");

int main()
{
	printf("이름 : %s \n", NAME);
	printf("나이 : %d \n", AGE);
	PRINTF_ADDR;
	return 0;
}
===============================실행결과=====================
이름 : 홍길동
나이 : 24
주소 : 경기도 용인시

매크로의 이름은 대문자로 정의하는 것이 일반적이다.

 


 #define _Function - like macro

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

매개변수를 가지고있으면 마치 함수와 유사하여 함수와 유사한 매크로, 또는 매크로 함수라 부른다.

 

#define SQUARE(X) X*X

 

즉 SQUARE(X) 라는 패턴이 등장시 X  * X 유형으로 바뀐다 .

예를 들어 

SQUARE(123);  =123 * 123

SQUARE(NUM);  =NUM * NUM

 

이렇게 변환의 결과가 마치 함수와 호출과 유사하다.

이렇게 선행처리기에 의해서 변환되는 과정 자체를 '매크로 확장(macro expansion)'이라 한다.

 

주의점

SQUARE(3+2)를 매크로 정의 한다면 어떻게 될까? 

함수의 관점에서 보면 5가 전달되는 게 맞지만 , 선행처리기는 단순히 3+2 * 3+2 로 치환이 된다. 즉 곱셈이 먼저 된다. 

이러한 문제점이 있다.

SQUARE((3+2))  안에 괄호를 치면

(3+2) *(3+2)로 치환되기 때문에 이문제는 해결이 가능하다.

즉 매크로를 정의할때 몸체에 괄호를 마구마구 쳐야 문제가 해결 가능하다.

 

매크로를 두줄에 걸쳐서도 정의가 가능한데 \문자를 사용하면된다

#define PI

 \ 3.14

 

매크로 정의시 사전에 정의된 매크로도 사용이 가능하다. 

#define SQUARE(X) X*X

#define PI 3.14

#define MUL(X) (SQUARE(X)*(PI))

 


매크로 함수는 일반 함수를 정의하는 것보다 복잡하다. 정의하고자 하는 함수의 크기가 크면, 매크로로 정의하는 것 자체가 불가능할 수 도 있다. 그럼에도 불구하고 함수를 매크로로 정의하는 이유를 알아보자.,

 

장점 

매크로 함수는 일반 함수에 비해 실행속도가 빠르다.

자료형에 따라서 별도로 함수를 정의하지 않아도 된다.

 

실행속도가 빠른이유는

호출된 함수를 위한 스택 메모리 할당

실행위치의 이동과 매개변수로의 인자 전달

return 문에 의한 값의 반환

 

따라서 함수의 빈번한 호출흔 실행속도의 저하로 이어진다. 반면 매크로 함수는 선행처리기에 매크로 함수의 몸체부분이 매크로 함수의 호출문장을 대신하기 때문에  실행속도 상에 이점이 있다.

또한 자료형에 상관없이 제대로 치환되기 때문에 좋다.

 

단점

정의하기가 까다롭다

디버깅하기가 쉽지않다.

 

즉 매크로함수들은 

1. 작은 크기의 함수

2. 호출의 빈도수가 높은 함수

를 매크로로 선언해야 정의하기가 편하고 에러의 발생확률도 낮아서 디버깅에 대한 염려를 덜수 있다. 

그리고 호출의 빈도수가 높아야 매크로 함수가 가져다 주는 성능의 이점도 최대한 누릴 수 있다.

 


조건부 컴파일(Conditional compilation)

매크로 지시자 중에는 특정 조건에 따라 소스코드의 일부를 삽입하거나 삭제할수록 디자인 된 지시자가 있다.

 

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

if문이 조건부 실행을 위한 것이라면, #if...#endif 는 조건부 코드 삽입을 위한 지시자이다. 

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

int main()
{
	int num1, num2;
    printf("두개의 정수 입력 : );
    scanf("%d %d", &num1, &num2);
    
#if ADD		//ADD가 참이라면
	printf("%d + %d =%d", num1, num2, num1+num2);
#endif

#if MIN		//ADD가 참이라면
	printf("%d - %d =%d", num1, num2, num1-num2);
#endif

	return 0;
}
========================실행 결과==========================
두 개의 정수 입력 : 5 4
5 + 4 =9

2행과 3행에 정의되어 있는 매크로 ADD와 MIN이 각각 1과 0인 관계로 12행은 삽입이 되지만, 16행은 삭제가 되어 위의 실행결과를 보이게 된다. 

#if 문 구성에는 연산자도 사용이 가능하다


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

#ifdef는 매크로가 정의되었는지, 안되었는지 기준으로 동작한다.

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

int main()
{
	int num1, num2;
    printf("두개의 정수 입력 : );
    scanf("%d %d", &num1, &num2);
    
#ifdef ADD		//ADD가 정의되었다면
	printf("%d + %d =%d", num1, num2, num1+num2);
#endif

#ifdef MIN		//ADD가 정의되었다면
	printf("%d - %d =%d", num1, num2, num1-num2);
#endif

	return 0;
}
========================실행 결과==========================
두 개의 정수 입력 : 7 2
7 - 2 = 5

2행과 3행에 정의되어있는 매크로의 값은 중요하지 않다. 매크로 몸체를 생략해서 정의해도 된다.

#define ADD

#define MIN


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

이 지시자의 조합은 위의 ifdef 지시자랑 반대의 개념이다. 

즉 매크로가 정의되어있지 않다면 ...   의 의미를 가지고 있다.

이 매크로는 헤더파일의 중복포함을 막기위해 주로 사용된다. 


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

if문에 else를 추가할수 있듯이#if, #ifdef, #ifndef문에도 #else문을 추가할 수 있다.

#elif의 삽입 : #if에만 해당

if문에 else if를 여러 번 추가할 수 있듯이, #if문에도 #elif를 여러번 추가할 수있다. 그리고 else if의 끝을 else로 마무리 할수 있듯이, #elif의 끝을 #else로 마무리할 수 있다.


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

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

STRING_JOB(홍길동, 나무꾼); 을 넣으면 

"홍길동의 직업은 나무꾼입니다."라고 나올 것으로 기대하는데 

선행처리기는 문자열 안에서 매크로의 매개변수 치환이 발생하지 않으므로 위의 문자열은 출력되지 않는다.

 

이문제를 해결할라면 #연산자를 사용해야 한다. 

#define STR(ABC) #ABC

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

 

이렇듯 #연산자는 치환의 결과를 문자열로 구성하는 연산자이다. 따라서 다음 두 문장은 ,

STR(123)

STR(12, 23, 34);

선행처리기에 의해

"123"

"12, 23, 34"

 

문자열은 나란히 선언하면, 하나의 문자열로 간주가 된다. 따라서 다음과 같이 문자열을 선언하는 것도 가능하다.

char * str= "ABC" "DEF";

그리고 이는 

char *str= "ABCDEF"  이 문자열 선언과 동일하다.

따라서 음과 같이 문자를 구성하는 것도 가능하다.

char * str = STR(12) STR(34);

그리고 이는 다음과 같이 치환된다.

char * str ="12" "34"

즉 

char *str ="1234"; 가 된다.

 

위의 코드로 STRING_JOB을 바꿔보면

#include <stdio.h>
#define STRING_JOB(A,B) #A"의 직업은" #B"입니다."

int main()
{
	printf("%s \n", STRING_JOB(홍길동, 나무꾼));
    printf("%S \n", STRING_JOB(홍길순, 사냥꾼));
    return 0;
}
==================================실행 결과==================================
홍길동의 직업은 나무꾼입니다.
홍길순의 직업은 사냥꾼입니다.

즉 실행결과를 보면

"홍길동" "의 직업은" "나무꾼" "입니다."

"홍길동의 직업은 나무꾼입니다."랑 똑같다.


특별한 연산자 없이 단순히 연결하는 것은 불가능하다.

대학의 학번은 일반적으로 다음과 같은 형태로 조합된다.

10 : 입학년도

65 : 학과 코드

175:고유번호 

최종학번 : 1065175

 

우리는 학번을 조합하는 매크로 함수를 정의하고자 한다.

STNUM(10, 65, 175);

그리고 이문장은 선행 처리기에 의해서 다음과 같이 치환되어야 한다.

1065175

 

필요한 형태대로 단순하게 결합할 방법이 있다.

바로 매크로 ##연산자 이다.

 

##연산자는 전달인자들이 단순히 이어진다. 즉 

매크로 함수의 전달인자를 다른대상(전달인자, 숫자, 문자, 문자열 등)과 이어줄 때 사용한다.

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

int num =CON(10, 20); 

int num = 100020; 이 되는 것이다.

즉 학번은

#define STNUM(Y, S, P) Y ## S ## P 로 매크로를 선언하면 된다.

 

'휴지통 > C 언어' 카테고리의 다른 글

C언어 함수들2  (0) 2021.02.25
C언어 함수들1  (0) 2021.02.11
메모리 관리와 동적할당  (0) 2021.02.10
파일입출력2  (0) 2021.02.07
파일 입출력1  (0) 2021.02.06