43. 매크로 구문

C언어에서는 자주 사용하는 표현을 약속하여 쉽게 표현할 수 있게 매크로 구문을 제공하고 있어요.

매크로 구문은 자주 사용하는 표현을 약속한 후 개발자가 매크로 구문을 사용하여 쉽게 코드를 작성하는 문법이예요.
개발자가 매크로 구문으로 코드를 작성하면 컴파일러가 기계어 코드로 번역하기 전에 약속한 구문으로 바꾸어 줘요.
이러한 과정을 전개라 부르며 컴파일 전에 수행해서 전처리 구문이라고도 불러요.

C언어에서는 #include문으로 다른 파일의 내용을 포함시키거나 #define문으로 매크로 구문을 약속하는 것이 대표적이죠.

#include <파일명>
#include “파일명”

#include 문은 다른 파일에 있는 내용을 포함시키는 매크로 구문이예요.
개발도구를 설치하면서 함께 설치한 파일들이 있는 폴더는 개발도구의 환경에 설정되어 있어요.
이처럼 개발도구 환경에 설정된 폴더 안에 있는 파일을 포함할 때는 #include <파일명>을 지정하죠.
그리고 프로젝트 폴더에서 상대 경로로 표현할 때는 #include “파일명”으로 지정해요.

C언어로 작성한 프로그램은 소스 파일(확장자가 .c인 파일은 소스 파일, 확장자가 .h인 파일은 헤더 파일) 단위로 컴파일해요.
하나의 프로그램을 개발할 때 프로젝트에 여러 개의 소스 파일로 구성하거나 이미 만들어진 라이브러리를 포함할 수 있어요.
이 때 다른 소스 파일이나 라이브러리에 있는 함수나 변수 등을 사용하기 쉽게 #include 문을 사용할 수 있어요.

C언어에서는 소스 파일 단위로 컴파일하며 다른 함수 등을사용하려면 사용하는 구문보다 선언문이 먼저 와야 해요.
이를 위해 소스 파일에 정의한 함수 등을 사용하기 편하게 헤더 파일에 선언문을 작성할 수 있어요.

//B.C
int Add(int a,int b) //함수 정의문
{
    return a+b; //a와 b를 더한 값을 반환
}
//A.C
int main()
{
    int result = 0; 
    result = Add(4,5); // 함수 호출식의 반환 값으로 대입
    printf("두 수의 합은 %d 입니다.\n",result);
    return 0;
}

위의 예처럼 B.C 소스에는 Add 함수가 있고 A.C 소스에는 Add 함수를 사용한다고 가정해요.
이때 컴파일러는 B.C 소스 파일과 A.C 소스 파일을 각각 컴파일한 후에 하나의 파일로 링킹하죠.
그런데 A.C 소스 파일에는 Add 함수를 몰라서 컴파일할 때 선언문이 필요해요.

//A.C
int Add(int a,int b); //함수 선언문
int main()
{
    int result = 0; 
    result = Add(4,5); // 함수 호출식의 반환 값으로 대입
    printf("두 수의 합은 %d 입니다.\n",result);
    return 0;
}

이처럼 매 번 다른 소스 파일에 정의한 것을 사용하는 곳에서 선언하는 것은 불편하겠죠.
이에 다른 소스 파일에서 사용할 것을 헤더 파일에 작성하면 사용하는 곳에서는 헤더 파일만 포함하면 쉽게 사용할 수 있어요.

◈ 두 개의 소스 파일로 구성한 프로젝트에서 다른 소스 파일에 있는 함수를 사용하는 예

//b.c
int Add(int a,int b) //함수 정의문
{
    return a+b; //a와 b를 더한 값을 반환
}
//b.h
#pragma once
int Add(int a,int b); //함수 선언문
//a.c
#pragma warning(disable:4996) //경고 메시지를 오류 목록에 표시하지 않음
#include <stdio.h>
#include "b.h"
int main()
{
    int a = 0, b = 0;
    int result = 0;
 
    printf("두 수를 입력하세요.\n");
    scanf("%d %d",&a,&b);
    fflush(stdin);
 
    result = Add(a,b); // 함수 호출식의 반환 값으로 대입
    printf("두 수의 합은 %d 입니다.\n",result);
    return 0;
} 

#define [매크로 상수] [표현식]
#define [매크로 함수(매크로 인자 리스트)] [매크로 인자를 포함한 표현식]

C언어에서는 매크로 정의문을 제공하고 있어요.
매크로 정의문은 매크로 표현에 대응하는 표현식을 약속하는 구문이예요.
개발자가 코드를 작성할 때 매크로 표현을 사용하면 약속한 표현식으로 전개하는 문법이죠.

만약 #define 문을 이용하여 매크로 상수를 정의하면 전처리가 코드에 이용한 부분을 약속한 상수로 코드를 전개해 줘요.

#define MAX_STUDENT    50
…중략…
if((num>=1) && (num<=MAX_STUDENT)
{
… 중략…
}

위처럼 작성한 코드가 있다면 전처리기가 다음과 같은 코드로 전개해요.
…중략…
if((num>=1) && (num<=50)
{
… 중략…
}

이와 같은 매크로 상수를 정의하여 프로그래밍하면 코드에 표현한 상수의 의미를 이해하기 쉬워지요.
그리고 상수 값을 바꿔야 할 때 매크로 상수 정의문만 고치기만 해도 알아서 바꿔줘서 유지  보수도 적게 들어요.

#define 문을 이용하면 매크로 상수 외에도 매크로 함수를 만들 수도 있어요.
#define 매크로 함수 이름(인자 리스트)   표현식

매크로 함수를 정의하면 사용하는 부분을 전처리기가 표현식으로 전개해줘요.

만약 #define MacroAdd(x,y)   x+y 처럼 정의하면 result = MacroAdd(3,4); 구문은 result = 3+4; 로 전개하겠죠.
그리고 result = MacroAdd(3,4)*5; 구문은 result = 3+4*5; 로 전개해요.
아마도 개발자는 3과 4를 더한 값에 5를 곱하여 35가 나오기를 기대하겠지만 곱하기 연산이 우선 순위가 높아서 23이 나와요.

따라서 매크로 함수를 정의할 때 표현식은 괄호로 묶어서 정의하세요.

만약 #define MacroAdd2(x,y)   (x+y)처럼 정의하면 result = MacroAdd2(3,4)*5;구문을 result = (3+4)*5; 로 전개해요.

하지만 #define MacroMul(x,y)   (x*y) 처럼 정의하면 result = MacroMul(3+2,4); 구문을 result = (3+2*4); 로 전개하죠.
여러분은 #define MacroMul2(x,y) ((x)*(y))처럼 인자도 괄호를 사용하여 정의하세요.
이렇게 정의하면 result=MacroMul2(3+2,4); 구문을 전처리기는 result = ((3+2)*4); 로 전개해 줘서 의도와 같아요.

◈ 매크로 함수를 정의할 때 표현식에 괄호가 있을 때와 없을 때 차이를 비교하는 예

#include <stdio.h>
#define MacroAdd(x,y)     x+y
#define MacroAdd2(x,y)   (x+y)
 
#define MacroMul(x,y)    (x*y)
#define MacroMul2(x,y)    ((x)*(y))
 
int main()
{
    int result = 0;
 
    result = MacroAdd(3,4)*5; // result = 3+4*5; 로전개
    printf("MacroAdd(3,4)*5 결과: %d \n",result);
 
    result = MacroAdd2(3,4)*5; // result = (3+4)*5; 로전개
    printf("MacroAdd2(3,4)*5 결과: %d \n",result);
 
    result = MacroMul(3+2,4)*5; // result = 3+2*4*5; 로전개
    printf("MacroMul(3+2,4)*5 결과: %d \n",result);
 
    result = MacroMul2(3+2,4)*5; // result = ((3+2)*(4))*5; 로전개
    printf("MacroAdd2(3+2,4)*5 결과: %d \n",result);
    return 0;
}

◈ 실행 결과

MacroAdd(3,4)*5 결과: 23
MacroAdd2(3,4)*5 결과: 35
MacroMul(3+2,4)*5 결과: 55
MacroMul2(3+2,4)*5 결과: 100