프로그래밍 언어/C

[선행처리] 선행처리기, 매크로 함수, 미리 정의된 매크로

순정법사 2023.08.17
더보기

 

C 언어에서 실행파일을 생성하는 순서 중 선행처리에 대한 부분을 공부할 예정!

 

 

A. 선행처리기

1. 선행처리

a. 선행처리(preprocess)란?

🌟 실행 파일을 생성하는 과정에서 소스 파일 내에 존재하는 선행처리 지시문을 처리하는 작업

 

컴파일 하기 전 선행처리기(preprocessor)에 의해 먼저 처리

코드 생성 ❌ 코드 소스 재구성 ⭕

 

b. 선행처리문의 특징

  • 선행처리 문자(#)로 시작
  • 코드 내 하나의 라인 모두 차지 (선행처리문 뒤에는 주석만 가능) 
  • 세미콜론 X
  • 소스 파일 어디에나 위치할 수 있지만, 선행처리문이 위치한 곳 부터 파일의 끝까지만 영향

 

 

2. 선행처리 지시자 (preprocessing directives)

a. 선행처리 지시자의 종류

선행처리 지시자 설명
#include 외부에 선언된 함수나 상수 등을 사용하기 위해, 함수나 상수가 포함된 외부 파일을 현재 파일에 포함할 때 사용함.
#define 함수나 상수를 단순화해주는 매크로를 정의할 때 사용함.
#undef #define 지시자로 이미 정의된 매크로를 삭제할 때 사용함.
#line __LINE__ 매크로와 __FILE__ 매크로를 재정의할 때 사용함.
#error 지정한 오류 메시지를 출력하고, 컴파일 과정을 중단하고자 할 때 사용함.
#pragma 프로그램의 이식성을 위해 운영체제별로 달라지는 지시사항을 컴파일러에 전달할 때 사용함.
#if, #ifdef, #ifndef,
#elif, #else, #endif
조건부 컴파일 지시자

 

b. #include

🌟 외부에 선언된 함수나 상수 등을 사용하기 위해서 헤더 파일을 현재 파일에 포함할 때 사용

 

선행처리기는 #include 지시자 뒤 파일(이름)을 현재 파일에 포함

 

💙 파일 이름을 표시하는 방법

 

1) #include <stdio.h>

 

C언어에서 제공하는 표준 헤더 파일을 포함할 때 <> 사용

표준 시스템 디렉터리 ➡ 현재 작업 디렉터리 순으로 헤더 파일을 찾음

 

2) #include "myStdio.h"

 

사용자가 직접 작성한 헤더 파일을 포함할 때 " " 사용

현재 작업 디렉터리 ➡ 표준 시스템 디렉터리 순으로 찾음

 

👉 방법에 차이는 없지만, 많은 개발자가 이 기준에 맞춰 코드를 작성함

 

c. #define

🌟 함수나 상수를 단순화해주는 매크로를 정의할 때 사용

 

매크로는 함수나 상수에 이름을 붙여 뭘 나타내는지 명확하게 알려줌 (가독성이 좋아짐)

매크로 확장 : 선행처리기가 식별자를 대체 리스트로 치환해주는 것

매크로끼리 중첩해서 사용 가능

 

📘 문법

#define 식별자 대체리스트

식별자 : 매크로(macro)라고 부름, 사용자가 미리 정의한 약어, 
	변수 이름 생성 규칙과 똑같은 생성 규칙을 따라서 작성

🤓 예제

#include <stdio.h>
#define PI 3.14  //2)

int main(void)
{
    double radius = 12;  

    printf("원주율을 나타내는 PI의 값은 %.2f입니다.\n", PI);	//1)
    printf("원의 면적은 %.2f * %.2f * %.2f = %.2f입니다.\n", PI, radius, radius, PI * radius * radius);
    
    return 0;
}

✨ 실행결과

원주율을 나타내는 PI의 값은 3.14입니다.
원의 면적은 3.14 * 12.00 * 12.00 = 452.16입니다.

 

📝 예제 설명

 

1) 문자열에 포함된 매크로 이름에 대한 치환 작업 

2) 객체 같은 매크로(object-like macro)

 

 

 


B. 매크로 함수

1. 매크로 함수

a. 매크로 함수란?

🌟 C 언어에서는 #define 선행처리 지시문에 인수로 함수의 정의를 전달하며 함수처럼 동작하는 매크로를 생성할 수 있음

이러한 매크로를 함수 같은 매크로(function-like macro) 또는 매크로 함수라고 함

 

🤓 예제

#include <stdio.h>
#define SUB(X,Y) X-Y
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)  

int main(void)

{
    int result;
    int num_01 = 15, num_02 = 7;  

    result = SUB(num_01, num_02);
    PRT(result);
    return 0;
}

✨ 실행결과

계산 결과는 8입니다.

 

예제를 필기해봤다

 

b. 함수와 매크로 함수의 차이점

일반 함수는 인수를 프로그램 실행 중일 때 전달받고, 매크로 함수는 인수를 컴파일 이전에 미리 치환함

(매크로 함수는 일반 함수처럼 보이지만 연산이 다른 시간대에 이루어짐)

 

🤓 예제 : 일반 함수와 매크로 함수와의 차이

#include <stdio.h>
#define SQR(X) X*X	//2)
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)  

int main(void)
{
    int result;
    int x = 5; 

    result = SQR(10);
    PRT(result);

    result = SQR(x);
    PRT(result);

    result = SQR(x+3);	//1)
    PRT(result);

    return 0;
}

✨ 실행결과

계산 결과는 100입니다.
계산 결과는 25입니다.
계산 결과는 23입니다.

 

📝 예제 설명

 

1) 선행처리 과정에서 매크로 함수의 x를 x+3으로 대체 (인수를 컴파일 이전에 미리 치환하기 때문에 이런 결과가 나타남)

2) x+3*x+3 = 5+3*5+3 = 5+15+3 = 23

 

💙 매크로 함수의 오류를 방지하기 위해 해야하는 일

 

  1. 매크로 함수의 전체를 괄호(())로 감싸기
  2. 매크로 함수의 인수들도 각각 괄호로 감싸기
  3. 매크로 함수를 호출할 때에는 증감 연산자(++, --)나 복합 대입 연산자 등은 지양하기

 

🤓 수정된 예제

#include <stdio.h>
#define SQR(X) ((X)*(X)) // 매크로 함수는 이처럼 모든 인수를 괄호로 묶어줘야 함.
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)  
 
int main(void)
{
    int result;
    int x = 5;  

    result = SQR(10);
    PRT(result);
    
    result = SQR(x);
    PRT(result);

    result = SQR(x+3);
    PRT(result);

    return 0;
}

✨ 실행결과

계산 결과는 100입니다.
계산 결과는 25입니다.
계산 결과는 64입니다.

 

c. 매크로 함수의 장단점

👍 장점

  • 매크로 함수는 단순 치환만 해 인수의 타입을 신경 ❌
  • 매크로 함수를 사용하면 여러 개의 명령문을 동시 포함 ⭕
  • 함수 호출에 의한 성능 저하 ❌ 프로그램의 실행속도가 👆

 

👎 단점

  • 원하는 결과를 얻는 정확한 매크로 함수의 구현은 어렵다 = 디버깅 또한 어려움
  • 함수의 크기가 증가 ➡ 사용되는 괄호 ⬆ 가독성 👎

 

 

2. #, ##연산자

🌟 #define 선행처리 지시문에서만 사용되는 선행처리기 연산자, 토큰 단위의 연산에서 사용

 

토큰(token) : 컴파일러가 인식하는 최소 단위의 문자나 문자열

 

a. # 연산자

🌟 매크로 함수의 대체 리스트 안의 인수 앞에 사용, 토큰을 문자열로 변환

 

해당 토큰은 실인수로 치환되면서 양쪽에 위치한 큰따옴표("")를 포함해 그대로 문자열 상수로 변환

즉, 문자열 안에 매크로 함수로 전달된 인수를 포함할 수 있음!

 

🤓 예제

#include <stdio.h>
#define SQR(X) printf(""#X"의 제곱은 %d입니다.\n", ((X)*(X)))  

int main(void)

{
    int x = 5;

    SQR(x);
    SQR(3+4);
    
    return 0;
}

✨ 실행결과

x의 제곱은 25입니다.
3+4의 제곱은 49입니다.

 

예제를 필기한 것

 

b. ## 연산자

🌟 두 개의 토큰을 하나의 토큰으로 결합해 주는 선행처리기 연산자

 

함수 같은 매크로뿐만 아니라 객체 같은 매크로의 대체 리스트에도 사용

변수나 함수의 이름을 프로그램의 런타임에 정의할 수 있음

 

🤓 예제

#include <stdio.h>
#define XN(n) x ## n  //1)

int main(void)
{
    int XN(1) = 10;	//2)
    int XN(2) = 20;  

    printf("x1에 저장된 값은 %d입니다.\n", x1); //3)
    printf("x2에 저장된 값은 %d입니다.\n", x2);
    return 0;
}

✨ 실행결과

x1에 저장된 값은 10입니다.
x2에 저장된 값은 20입니다.

 

📝 예제 설명

 

1) 문자 결합(토큰결합)을 하는 ## 연산자를 가지고 있는 매크로 함수인 XN()에 의해 2) XN(1)은 함수의 정의대로 x1 = 10이 된다3) 따라서 아래 출력되는 값이 10이 됨

 


 

💫 심화과정 : #, ##연산자를 사용해 동적으로 작성한 변수 예제

#include <stdio.h>
#define XN(n) x ## n	// 1) 토큰 결합을 하는 매크로 함수
#define PRT_XN(n) printf("x"#n"에 저장된 값은 %d입니다.\n", x ## n)  // 3) 출력되는 값을 동적으로 생성

int main(void)
{
    int XN(1) = 10;		// 2) x1 = 10
    int XN(2) = 20;		//   x2 = 20


    PRT_XN(1);	// 4)1 그대로 출력되어 x + 1, 즉, x1이 되고, 뒤에 값은 2) 에서 생성한 변수값이 들어감
    PRT_XN(2);
    return 0;
}

✨ 실행결과

x1에 저장된 값은 10입니다.
x2에 저장된 값은 20입니다.

 

 

 


C. 미리 정의된 매크로

1. 미리 정의된 매크로(predefined macro)

a. 대표적인 미리 정의된 매크로

C언어에서는 컴파일러가 참고해야 할 정보를 알려주기 위해서 몇몇 매크로를 미리 정의하여 제공

미리 정의된 매크로는 #define 선행처리 지시자로 정의하지 않아도 사용할 수 있으나, 재정의 ❌

 

미리 정의된 매크로 설명
__DATE__ 선행처리가 수행된 날짜를 "Mmm dd yyyy"형식으로 나타낸 문자열
__TIME__ 선행처리가 수행된 시간을 "hh:mm:ss"형식으로 나타낸 문자열
__FILE__ 현재 소스 파일의 이름을 나타내는 문자열
__LINE__ 현재 소스 파일에서 처리중인 라인 번호를 나타내는 문자열
__STDC__ 컴파일러가 C언어 표준을 따르면 1로 설정함.
__STDC_HOSTED__ 호스트 환경이 아니면 0, 호스트 환경이면 1로 설정함.

 

🤓 예제

#include <stdio.h>  

int main(void)

{
    printf("선행처리가 수행된 날짜는 %s입니다.\n", __DATE__);
    printf("선행처리가 수행된 시간은 %s입니다.\n", __TIME__);
    printf("현재 소스 파일에서 처리중인 라인 번호는 %d입니다.\n", __LINE__);
    printf("__STDC__ : %d\n", __STDC__);
    printf("__STDC_HOSTED__ : %d\n", __STDC_HOSTED__);
    return 0;
}

✨ 실행결과

선행처리가 수행된 날짜는 Feb 15 2017입니다.
선행처리가 수행된 시간은 17:03:55입니다.
현재 소스 파일에서 처리중인 라인 번호는 7입니다.
__STDC__ : 1
__STDC_HOSTED__ : 1

 

b. #line

🌟 __LINE__ 매크로와 __FILE__ 매크로를 재정의할 수 있게 함

 

라인 번호는 int형 타입으로, 파일명은 문자열로 전달

사용자가 직접 사용하기보다는 주로 컴파일러가 오류 메시지를 위해 사용

 

c. #error

🌟 지정한 오류 메시지를 출력하고, 컴파일 과정을 중단

 

주로 조건부 컴파일 선행처리 지시자(#if #ifdef... 등)와 함께 사용하여 디버깅에 사용

 

d. #undef

🌟 이미 정의되어 있는 매크로를 취소하는 동작을 수행 (#define 선행처리 지시자와 반대)

 

따라서 #define 선행처리 지시자에 의해 정의되는 매크로가 치환하는 범위 :

➡ #define 지시자가 정의된 위치 ~ #undef 지시자에 의해 취소되는 위치나 파일의 끝

 

💥 WARNING

살펴본 미리 정의된 매크로는 사용자가 임의로 정의를 취소하거나 변경 ❌❌

 

 

 


출처 : http://www.tcpschool.com/c/c_prepro_preprocessor

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com