회사에서 SWIG 인터페이스 파일을 코드 분석 하는데 #define이라는 것이 나와서 뭔지 알아보고자 함
#define을 이해하기 위해선 #define을 포함하는 '전처리기' 및 '지시자'라는 개념을 알아야 해서 이 용어들을 먼저 이해해보고자 하다가 #define에 집중하기 보단 '전처리기'가 무엇인지, 어떠한 전처리기가 있는지에 대한 내용을 다루는 것이 더 나을 것 같아 '전처리기'에 대해서 정리해보고자 함
01 전처리기 (Preprocessor, Precompiler)란
0. 개요
우리는 C 언어로 어떤 프로그램을 만들 때 소스 코드를 짜고, 컴파일(기계어화)을 한 후, 링크를 한다
'전처리기(Preprocessor, Precompiler)'는 소스 코드를 짠 후 컴파일 하기 직전에 처리하는 컴파일러의 한 부분이다
1. 정의
컴퓨터의 처리에 있어 중심적인 처리를 수행하는 부분을 위해 사전 준비적인 계산을 행하는 프로그램
구체적으로, 프로그램 생성 시 소스 코드를 짠 후컴파일 하기 직전에 처리하는 컴파일러의 한 부분
전처리기에서는 몇 가지 '지시자 (directives)'들을 처리함 - 전처리기 지시자(preprocessor directives)라고 칭함
// 예시
#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)' 메시지로 만듬
// 따라서 취약한 함수 등에서 발생하는 경고 메시지를 전부 오류로 변환하므로 강하게 규제 가능