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)' 메시지로 만듬
// 따라서 취약한 함수 등에서 발생하는 경고 메시지를 전부 오류로 변환하므로 강하게 규제 가능
참조
- https://blog.naver.com/ndb796/221046613546
- (역할 및 중요성, 종류) https://blog-of-gon.tistory.com/143
- (#undef) https://www.geeksforgeeks.org/define-vs-undef-in-c-language/
- (조건처리 예제) https://learn.microsoft.com/ko-kr/cpp/preprocessor/hash-if-hash-elif-hash-else-and-hash-endif-directives-c-cpp?view=msvc-170
- (#ifdef) https://learn.microsoft.com/en-us/cpp/preprocessor/hash-ifdef-and-hash-ifndef-directives-c-cpp?view=msvc-170
- (#ifndef) https://blog.naver.com/sharonichoya/220507818075
'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 |