프린세스 다이어리

[C] 전처리기를 사용하여 프로그램을 작성하는 방법 본문

C, C++

[C] 전처리기를 사용하여 프로그램을 작성하는 방법

개발공주 2021. 10. 12. 23:17
728x90

 

전처리기 구문은 다른 프로그램 영역과 독립적으로 처리되고, 소스코드 파일 단위로 효력이 존재한다는 특성이 있다. 전처리기 사용은 필수는 아니다. C언어에서는 보다 이식성, 생산성 높게 안정적으로 코드를 작성하기 위해 활용된다.

 

1. 파일 포함 전처리기

 

1. #include는 전처리기에서 가장 많이 사용되는 문법이다.

2. 특정한 파일을 라이브러리로서 포함시키기 위해 사용한다.

3. #include 구문으로 가져올 수 있는 파일에는 제약이 없다.

 

#include "filename"
#include <filename>

특정한 외부 파일을 라이브러리의 형식으로 현재 소스에 불러오기 위해서 사용한다. 어떤 파일이든 현재의 소스코드 파일로 가져올 수 있고 이 include라는 전처리 구문 자체를 그 파일의 전체 소스코드로 대체한다는 특징이 있다. 

 

#include <filename>

1. 위와 같은 식으로 선언하면 시스템 디렉터리에서 파일을 검색한다.

2. 운영체제마다 시스템 디렉토리가 존재하는 경로가 다를 수 있다.

3. 대표적으로 stdio.h와 같은 헤더 파일 등이 시스템 디렉터리에 존재한다. 

 

기본적으로 우리가 C언어 컴파일러를 구축하게 되면 다양한 stdio.h 같은 라이브러리들이 기본적으로 우리 컴퓨터 내에 포함이 되는데 거기에서 검색해서 우리의 프로그램 상에 띄워 준다는 의미를 가진다. 이게 없으면 기본적인 입출력도 못한다.

 

#include "filename"

1. 위와 같이 선언하면, 현재 폴더에서 파일을 먼저 검색한다.

2. 만약 현재 폴더에 파일이 없다면 시스템 디렉토리에서 파일을 검색한다.

 

2. 헤더 만들어서 불러오기

 

직접 헤더파일을 만들어서 내 소스에 가져오는 간단한 예시를 작성해보았다. 따라서 의도치 않게 한 번 참조한 헤더 파일을 여러 번 참조하지 않도록 유의해야 한다. 

 

main.c 파일이 존재하는 경로에 temp.h 파일을 만들어 간단한 add 함수를 작성했다.

int add(int a, int b) {
 return a + b;
}

 

main.c 소스에서 #include 구문을 이용해서 현재 소스의 디렉터리 내에 존재하는 파일을 불러와서, 헤더 파일의 add함수를 그대로 가져다 썼다. 자바스크립트에서처럼 import 구문으로 특정 로직의 이름을 지정하지 않고 헤더 파일 자체를 불러오는 것이다.

#include <stdio.h>
#include "temp.h"
 
int main(void) {
    printf("%d\n", add(3, 8));
}
11

 

결과는 잘 나온다. #include 구문으로 작성된 부분을 풀어보면 다음과 동일한 의미를 가진다. #include를 이용해 가져오는 파일은 말그대로 파일의 소스코드 자체를 다 가져와서 치환을 하는 것이다.

#include <stdio.h>

int add(int a, int b) {
 return a + b;
}
 
int main(void) {
    printf("%d\n", add(3, 8));
}

 

3. 매크로 전처리기

 

1. 프로그램 내에서 사용되는 상수나 함수를 매크로 형태로 저장하기 위해 사용한다.

2. #define 문법을 사용해 정의할 수 있다. 

 

상수를 #define 해보기

 

#include <stdio.h>
#define PI 3.1415926535
 
int main(void) {
    int r = 10;
    printf("원의 둘레: %.2f\n", 2 * PI * r);
}
원의 둘레: 62.83

프로그램을 실행해 보면, 상수인 PI 값을 인식하여 둘레를 구한다.

 

인자를 가지는 함수 형태의 매크로 전처리기

 

#include <stdio.h>
#define POW(x) (x * x)
 
int main(void) {
    int x = 10;
    printf("x의 제곱: %d\n", POW(x));
}
x의 제곱: 100

 

위와 같이 자주 사용하는 함수를 define 으로 간단하게 정의할 수 있다. 

 

define 문법은 실제로 많이 사용되는데 특히 알고리즘 문제 등 시간이나 코드의 양을 획기적으로 줄여야 할 때 많이 활용된다. 

#include <stdio.h>
#define ll long long
#define ld long double
 
int main(void) {
    ll a = 987654321000;
    ld b = 133.5050;
    printf("%.1Lf\n", a * b);
}
131856790125105.0

예를 들어, long long 과 long double 은 글자 자체가 길기 때문에 짧게 줄여서 정의해 둔 다음 필요할 때마다 가져다가 사용할 수도 있다. 

 

4. 조건부 컴파일

 

1. 조건부 컴파일은 컴파일이 이루어지는 영역을 지정하는 기법이다.

2. 일반적으로 디버깅과 소스코드 이식을 목적으로 작성된다.

3. C언어로 (하드웨어와 가까운)시스템 프로그램을 작성할 때는 운영체제에 따라 소스코드가 달라질 수 있다.

4. 이 때, 운영체제에 따라 소스코드의 이식성을 고려해, 컴파일이 수행되는 소스코드를 다르게 할 수도 있다.

 

조건부 컴파일에서 가장 많이 사용되는 문법은 바로 #ifndef ~ #endif 문법이다. 만약 아래와 같이 헤더 파일을 두 번 중복해서 올렸다고 생각해 보자, 그러면 동일한 헤더 파일이 중복되었다는 에러 메시지를 띄워 준다. 

#include <stdio.h>
#include "temp.h"
#include "temp.h"

int main(void) { 
    printf("%d\n", add(3, 5));
}
In file included from main.c:3:
./temp.h:1:5: error: redefinition of 'add'
int add(int a, int b) {
    ^
./temp.h:1:5: note: previous definition is here
int add(int a, int b) {
    ^
1 error generated.

 

#ifndef ~ #endif 구문을 사용해서 temp.h 파일이 정의되어 있지 않다면 해당 구문 안의 코드를 컴파일하겠다는 의미다. if문 안에서는 _TEMP_H_ 파일을 정의할 수 있도록 하면 된다.

#ifndef _TEMP_H_
#define _TEMP_H_

int add(int a, int b) {
 return a + b;
}

#endif

 

그렇게 하면, main.c 파일에서 실수로 여러 차례 temp.h 헤더파일을 불러왔다고 해도, if문에 의해 한 번만 불러올 수 있게 된다. 

 

5. 파일 분할 컴파일

 

일반적으로 자신이 직접 라이브러리를 만들 때에는 C언어 파일과 헤더 파일을 완전히 분리해서 모두 작성해야 한다. 다음과 같이 소스를 분리해서 목적에 맞게 관리를 할 수 있다. 

// main.c

#include <stdio.h>
#include "temp.h" 

int main(void) { 
    printf("%d\n", add(3, 5));
}
// temp.h

#ifndef _TEMP_H_
#define _TEMP_H_

int add(int a, int b) {};

#endif
// temp.c

#include "temp.h"

int add(int a, int b) {
 return a + b;
}
728x90
Comments