본문 바로가기
C

전처리기 (Preprocessor, Precompiler)

by yororing 2024. 6. 19.

00 개요

  • 회사에서 SWIG 인터페이스 파일을 코드 분석 하는데 #define이라는 것이 나와서 뭔지 알아보고자 함
  • #define을 이해하기 위해선 #define을 포함하는 '전처리기' 및 '지시자'라는 개념을 알아야 해서 이 용어들을 먼저 이해해보고자 하다가 #define에 집중하기 보단 '전처리기'가 무엇인지, 어떠한 전처리기가 있는지에 대한 내용을 다루는 것이 더 나을 것 같아 '전처리기'에 대해서 정리해보고자 함

01 전처리기 (Preprocessor, Precompiler)란

0. 개요

  • 우리는 C 언어로 어떤 프로그램을 만들 때 소스 코드를 짜고컴파일(기계어화)을 한 후, 링크를 한다
  • '전처리기(Preprocessor, Precompiler)'는 소스 코드를 짠 후 컴파일 하기 직전에 처리하는 컴파일러의 한 부분이다

1. 정의

  • 컴퓨터의 처리에 있어 중심적인 처리를 수행하는 부분을 위해 사전 준비적인 계산을 행하는 프로그램
  • 구체적으로, 프로그램 생성 시 소스 코드를 짠 후 컴파일 하기 직전에 처리하는 컴파일러의 한 부분
  • 전처리기에서는 몇 가지 '지시자 (directives)'들을 처리함 - 전처리기 지시자(preprocessor directives)라고 칭함
    • 이 지시자들은 '#'라는 기호로 시작, 뒤에 ';' 안붙임
    • 예: #include, #define, #undef, #pragma, #if / #ifdef / #ifndef / #elif / #else / #endif, #error 등

2. 역할 및 중요성

  • 전체적으로는, 불필요한 소스 코드 작성을 줄여 좋은 소스 코드 구성 생성에 도움을 줌
  • 버전 관리, 이미 작성된 다른 라이브러리 가져오기, 함수 정의, 상수 정의 등 다양한 목적으로 사용 가능

02 종류

1) 파일 처리 - #include

  • include = '포함시키다'
  • 외부에서 선언된 다양한 파일, 소스 코드, 라이브러리를 포함/참조 시 사용됨 (python의 import 같은 개념)
  • < >로 정의 시 컴파일러의 표준 포함 디렉터리에서 파일을 찾고 " "로 정의 시 현재 디렉토리를 기준으로 파일을 찾음
#include <내장_헤더파일명.h>
// 또는
#include "사용자정의_헤더파일명.h"
  • 예시)
#include <stdio.h>

int main(void) {
    printf("Hello Wrld");
    rerturn 0;
}
  • 가장 기본적인 라이브러리인 stdio를 참조하여 printf() 함수 사용 가능케 함 (stdio.h라는 해더 파일에는 굉장히 많은 환경 설정과 관련한 코드가 작성되어 있음)

2) 형태 정의 - #define, #undef

  • define = '정의한다'
  • #define은 함수/상수들의 심볼릭화, 매크로화 및 다양한 인자들에 대한 정의 시 사용됨
  • 장점: 가독성이 상당히 높아짐

2-1) 상수 정의

#include <stdio.h>
#define ID 1234

int main(void){
    printf("%d\n", ID);
    return 0;
}

 

  • 위 소스 코드는 전처리기의 가장 일반적인 용도로서, ID라는 상수를 정의함
  • ID를 1234로 정의해서 아래쪽 코드에서 사용할 수 있게 함 

2-2) 함수 매크로 정의

#define SQUARE(x) ((x)*(x))
  • 치환 텍스트를 이용하면 함수와 완전히 동일한 기능으로 작동시킬 수 있음
  • 완전히 매크로 형태이기 때문에 텍스트가 치환될 뿐이라고 할 수 있음
  • 또한, 다음과 같은 방식으로 아예 괄호를 삽입한 형태의 함수 삽입 가능 
#define add(x,y) \
{\
    printf("%d+%d=%d\n", (x), (y), (x) + (y)); \
}
  • 내장 매크로도 있음!
    • 내장 매크로는 기본적인 C 언어 문법이 지원해서 정의된 매크로를 의미
    • 예) __DATE__, __TIME__

2-3) 정의된 내용 삭제

  • #define으로 정의된 내용들을 #undef를 사용하여 삭제 가능
// 예시
#include <stdio.h>
#define PI 3.14	// PI라는 상수 선언
#undef PI	// PT 상수 삭제

int main()
{
    int r = 6;
    float a;
    a = PI * r * r;	// PI가 정의되지 않았기에 이 계산은 compilation error가 날 것
    printf("area of circle is %f", a);
    return 0;
}
  • output

3) 조건 처리 - #if / #elif / #else / #ifdef / #ifndef / #endif

  • 조건부 컴파일 지시 키워드로서 조건문과 흡사
  • 컴파일러에게 소스 코드의 컴파일 영역을 알려줌
  • #if로 시작, 필요에 따라 #elif, #else 추가 가능, #endif로 끝맺음
#include <stdio.h>
#define HELLO "안녕"
#define CHECK 1

int main(void) {
#if CHECK == 1
    printf("Hello 이름을 정의했습니다. \n");
    printf("Hello 이름: %s\n", HELLO);
#endif
    return 0;
}
  • output

 

// 다음의 #if 및 #endif 지시문은 세 가지 함수 호출 중 하나의 컴파일을 제어함
#if defined(CREDIT)
    credit();        // CREDIT 식별자가 정의된 경우 credot에 대한 함수 호출이 컴파일됨
#elif defined(DEBIT)
    debit();        // DEBIT 식별자가 정의된 경우 debit에 대한 함수 호출이 컴파일됨
#else
    printerror();    // 식별자가 정의되지 않은 경우 printerror에 대한 호출이 컴파일됨
#endif
  • #ifdef로 특정한 값의 선언 여부 확인
    • #ifdef는 #if defined와 같은 역할을 함
#include <stdio.h>
#define TEST
void main() {
#ifdef TEST
    printf("TEST - DEFINED!\n");    // TEST가 정의되어 있다면 해당 줄 컴파일
#else
    printf("TEST - UNDEFINED.\n");    // TEST가 정의되어 있지 않다면 해당 줄 컴파일
#endif
}
  • 위와 같이 TEST가 정의되어 있는지 여부만 따짐. TEST의 값을 10으로 하든, 0으로 하든, 정의하지 않고 위와 같이 단순히 정의만 해도 #ifdef TEST는 참이 됨.
  • output

  • #ifndef로 현재 정의되어 있지 않다면 정의
    • #ifndef는 #if !defined와 같은 역할을 함 
    • 주로 헤더파일을 include할 때 이중포함 및 컴파일을 방지하는 방법으로 사용됨 (헤더파일의 처음과 끝에 매크로를 이용하여 오류 방지)
    • 예) 우리가 소스 코드 내에서 #include <stdio.h>를 여러 번 선언하더라도 문제되지 않는 이유는 stdio.h 내부에서 다음과 같이 선언되어있기 때문
#ifndef STDIO_H__
#define STDIO_H__

// 헤더파일의 내용

#endif
  • 결과적으로, STDIO_H__가 한 번 정의 되어있으면 다시 정의되지 않게 설정하였기에 같은 매크로가 여러 번 선언되는 오류 방지

4) 컴파일 옵션 처리 - #pragma

  • 프로그램의 이식성을 위해 컴파일러에게 특정한 명령 내릴 때 사용됨
#pragma once	// stdio.h는 단 한 번만 컴파일이 이루어지는 명령
#include <stdio.h>

#pragma warning(disable: 4996) 
// 경고번호 4996에 해당하는 경고는 더이상 출력되지 않음
// 대표적으로 현재 취약하다고 알려진 함수인 scanf를 사용하면 경고 문구를 출력하는 경우가 있음
// 이 때 위와 같이 #pragma 전처리문 사용 시 경고 문구를 성공적으로 삭제 가능

#pragma warning(error: 4996) 
// 이는 반대로 경고 메시지 자체를 '오류(Error)' 메시지로 만듬
// 따라서 취약한 함수 등에서 발생하는 경고 메시지를 전부 오류로 변환하므로 강하게 규제 가능

참조

  1. https://blog.naver.com/ndb796/221046613546 
  2. (역할 및 중요성, 종류) https://blog-of-gon.tistory.com/143
  3. (#undef) https://www.geeksforgeeks.org/define-vs-undef-in-c-language/
  4. (조건처리 예제) https://learn.microsoft.com/ko-kr/cpp/preprocessor/hash-if-hash-elif-hash-else-and-hash-endif-directives-c-cpp?view=msvc-170
  5. (#ifdef) https://learn.microsoft.com/en-us/cpp/preprocessor/hash-ifdef-and-hash-ifndef-directives-c-cpp?view=msvc-170
  6. (#ifndef) https://blog.naver.com/sharonichoya/220507818075 
  7.  
  8.  

'C' 카테고리의 다른 글

Declaration vs Definition (선언과 정의)  (0) 2024.06.24
typedef (자료형 별칭 부여 키워드)  (0) 2024.06.21
strcpy() strcpy_s() (문자열 복사 함수)  (0) 2024.06.21
struct (구조체)  (0) 2024.06.21
sizeof() 연산자  (0) 2024.06.21