#if, #ifdef, #ifndef, #else, #elif, #endif
|
조건 처리
|
조건에 맞춰 처리하는 전처리
- #if ~ #elif ~ #else : 참이라면, 또는 참이라면, 그외 (if ~ else if ~ else문과 사용법이 같음)
- #ifdef : 만약 정의되어있다면
- #ifndef : 만약 정의되어 있지 않다면
- #endif : #if, #ifdef, #infdef의 끝을 알린다.
</ul>
</td>
</tr>
#line
|
다음 행 번호 지정
|
소스 코드에서 다음 행 번호 지정
|
#pragma
|
구현에 따라 다른 동작
|
시스템에 따라 다른 설정을 규정할 때 사용
|
</table>
이제부터 하나하나 살펴보죠.
## #include
파일을 포함(인클루드)하는 지시어입니다. 다음과 같이 씁니다.
```
#include <파일명>
#include "파일 경로+파일명"
```
<>(꺽쇠)로 묶으면 헤더 파일은 컴파일러 표준 포함 경로에서 파일을 검색합니다.
```
#include // 컴파일러 표준 경로
```
""(큰따옴표)로 묶으면 현재 소스 디렉터리 경로를 포함합니다. 상대 경로와 절대 경로 모두를 지원합니다.
```
#include "stdio.h" // 현재 소스 파일이 있는 경로 포함
#include "..\stdio.h" // 현재 소스 파일이 있는 상위 디렉터리(상대 경로)
#include "c:\stdio.h" // C 드라이브 루트에 있는 파일(절대 경로)
```
#include 지시어의 사용법은 이게 전부입니다.
다만 윈도우 계열과 유닉스 계열은 디렉터리 구분자가 각각 \(\)와 /로 다릅니다. 따라서 유닉스 계열에서는 다음과 같이 /를 사용해 경로를 지정합니다.
```
#include "../stdio.h" // 현재 소스 파일이 있는 상위 디렉터리(상대 경로)
#include "/stdio.h" // 루트 디렉터리에 있는 파일(절대 경로)
```
그렇다면 #include는 헤더 파일을 추가할 때만 사용하는 걸까요?
그렇지 않습니다. 프로그램에서 어떤 데이터든 프로그램에서 사용할 필요가 있다면 가능합니다. 예를 들어 비트맵 이미지를 인클루드할 수도 있습니다.
#include는 지금껏 수차례 사용했으므로 어렵지 않을 겁니다.
## #define
#define 매크로를 지정하는 전처리 지시어입니다.
매크로 지정은 다음과 같이 합니다.
```
#define 식별자 대체대상코드
#define 식별자(매개변수 목록) 매개변수가_포함된_대체대상코드
```
* 식별자 : 매크로 이름입니다. 영문으로는 identifier라고 합니다.
* 대체대상코드 : 전처리기가 코드에 있는 매크를 대체할 대상입니다. 영문으로는 replacement token list라고 합니다. 편의상 의미가 쉽게 전달되도록 우리말로 풀어 썼습니다.
매크로명과 대체대상코드에 매개변수를 포함할 수 있습니다.
우선은 매개변수가 없는 경우를 살펴보겠습니다.
```
#include
#define HELLO "Hello"
int main(void) {
printf("%s\n",HELLO);
return 0;
}
```
[결과]
```
Hello
```
너무 쉬운 예제를 보니, 이걸 굳이 매크로로 지정해야 싶은 생각도 들 겁니다. enum이나 const로 상수를 지정할 수도 있으니까요. 그런데 전처리 과정에서 #define으로 상수를 지정하면 코드 전처리 과정에서 코드에 해당 값이 일괄 대입됩니다.
예를 들어 위 코드에서 #define HELLO "Hello"를 전처리하면 다음과 같이 코드가 변경됩니다.
```
#include
int main(void) {
printf("%s\n","Hello");
return 0;
}
```
HELLO가 아예 "Hello"로 대체되었습니다. const 상수로 지정하면 메모리 특정 위치에 자리잡게 되므로 CPU가 활용하려면 주소 연산이 필요합니다. 그런데 매크로는 사용할 코드 자체이므로 별도의 주소 연산이 필요없습니다. 실행 속도면에서 매크로가 우위에 있습니다.
또한 활용면에서도 우위에 있습니다.
매개변수가 있는 매크로를 예를 들어보죠. 다음과 같이 두 매개변수를 받아 더하는 매크로를 만들 수도 있습니다.
```
#include
#define ADD(x, y) x + y
int main(void) {
printf("%d\n",ADD(1, 2));
return 0;
}
```
[결과]
```
3
```
ADD 함수를 만들 때보다 간단 명료하죠? 게다가 (함수 호출이 없어) 실행도 빠릅니다.
여러 줄에 걸쳐 매크로를 만들 수도 있습니다. \를 행 끝에 써주면 다음 행까지 이어서 정의하겠다는 뜻입니다. 따라서 제일 마지막 행에는 \를 붙이지 않습니다. 외우기 힘드시다면 한 줄 일 때 \를 붙이지 않는 점을 착안하면 됩니다. 한 줄은 그 자체가 제일 첫 행이면서 제일 마지막 행이니까요!
여러 행 매크로의 극단적으로 이상해 보이는 예를 살펴보겠습니다.
```
#include
#define MAIN int main(void) { \
printf("hello world\n"); \
return 0; \
}
MAIN
```
[결과]
```
hello world
```
놀랍게도 main() 함수 자체를 매크로로 만들 수도 있군요!
이처럼 매크로 용도는 #define과 const 상수로 할 수 다양한 기능을 제공합니다.
요약하면 다음과 같은 장점이 있네요.
* 실행 속도를 높인다.
* 함수를 상대적으로 간단히 만들 수 있다.
물론 다음과 같은 단점도 있습니다.
* 남발하면 코드 해석이 어렵습니다.
* 재귀 호출이 안 된다.
### C 언어에서 지원하는 매크로
#define으로 사용자 지정 매크로를 지정할 수 있다. 그런데 C 언어 자체에서 지원하는 매크로도 있다.
매크로
|
설명
|
자료형
|
__FILE__
|
컴파일 하는 파일의 전체 파일 경로
|
문자열
|
__LINE__
|
코드에서 현재 행 번호
|
int형
|
__DATE__
|
시스템의 현재 시각
|
문자열
|
__TIME__
|
a string that holds the current system time
|
문자열
|
__func__
|
현재 함수명 출력(C99부터 지원)
|
문자열
|
__STDC__
|
컴파일러가 ANSI C 표준을 준거하면 1
|
int형
|
각각 어떤 기능인지 확인해보자.
```
#include
int main(void) {
printf("__FILE__ : %s (%dbyte)\n", __FILE__, sizeof(__FILE__));
printf("__LINE__ : %d (%dbyte)\n", __LINE__, sizeof(__LINE__));
printf("__DATE__ : %s (%dbyte)\n", __DATE__, sizeof(__DATE__));
printf("__TIME__ : %s (%dbyte)\n", __TIME__, sizeof(__TIME__));
printf("__func__ : %s (%dbyte)\n", __func__, sizeof(__func__));
printf("__STDC__ : %d (%dbyte)\n", __STDC__, sizeof(__STDC__));
return 0;
}
```
[결과]
```
__FILE__ : prog.c (7byte)
__LINE__ : 6 (4byte)
__DATE__ : Mar 13 2019 (12byte)
__TIME__ : 11:39:30 (9byte)
__func__ : main (5byte)
__STDC__ : 1 (4byte)
```
* __FILE__ : 파일 이름은 경우에 따라 다를 수 있습니다.
* __LINE__ : '#include '가 첫 번째 행입니다. 빈 행도 세면 6번째 행이 맞습니다.
* __DATE__ : 오늘 날짜입니다. 언젠가이 글을 보면 감회가 새롭겠군요.
* __TIME__ : 현재 시각입니다.
* __func__ : 현재 수행 중인 함수명을 출력합니다. C99부터 지원합니다.
* __STDC__ : ANSI C 표준 컴파일러가 맞군요!
## #undef
#undef은 매크로를 삭제합니다.
다음과 같이 사용합니다.
```
#undef 삭제할_식별자
```
사용법이 간단하니 예문은 생략합니다.
## #if-else
전처리 과정에서 조건 처리를 하는 지시어입니다.
#if와 #else뿐만 아니라 다른 지시어도 같이 씁니다.
다음 표에서 전처리 지시어를 확인할 수 있습니다.
[표] 전처리 지시어
전처리 지시어
|
설명
|
#if
|
전처리 조건문 #if문 시작
|
#elif
|
전처리 조건문 #if문의 특정 경우 (if~else문에서 else if에 대응함)
|
#else
|
전처리 조건문 #if문의 그외 경우
|
#ifdef
|
전처리 조건문, 만약 정의되어 있다면
|
#ifndef
|
전처리 조건문, 만약 정의되지 않다면
|
#endif
|
전처리 조건문의 끝을 알림
|
이 지시어를 서로 섞어 사용할 수 있습니다.
사용 예를 위주로 살펴보겠습니다.
### #if ~ #endif
사용법은 if~else문과 거의 같지만, 끝에 #endif가 붙는 점이 다르군요.
```
#if 조건식
// 실행할 명령
#else
// 실행할 명령
#endif
```
예를 들어 OS 버전에 따라 다른 조건을 설정하고 싶을 수 있습니다.
```
#if WIN64 == TRUE
#define LONG_DOUBLE_SIZE 8
#else
#define LONG_DOUBLE_SIZE 4
#endif
```
이 코드는 64비트 윈도우라면 long doule의 크기를 8바이트로 하고, 그렇지 않은 4바이트로 합니다. 실제로 운영체제나 CPU에 따른 최적화 전처리 구문은 멀티 플랫폼 프로그램에서는 흔히 사용됩니다.
### #ifndef ~ #endif
식별자가 정의되어 있지 않으면 다음 행부터 #endif까지 실행합니다.
다음과 같은 양식으로 사용합니다.
```
#ifndef 식별자
// 실행할 명령
#endif
```
예를 하나 들어보겠습니다. 익숙한 헤더 파일 stdio.h을 다음과 같이 두 번 연속 코드에 썼다고 합시다. 과연 전처리기는 이 헤더 함수를 두 번 인클루드할까요?
```
#include
#include
```
상식적으로 생각해 두 번 인클루드하면 실행 파일이 커지니까 좋지 못한 것 같습니다. 무언가 수를 떠올려 두 번 인클루드되는 상황을 막아야겠군요.
다음과 같은 코드를 헤더 파일에 포함 시키면 어떨까요?
```
#ifndef _STDIO_H_
#define _STDIO_H_
// 필요한 정의하기
#endif
```
식별자 _STDIO_H_가 없다면 _STDIO_H_를 정의하고 필요한 정의를 하는 문장입니다. 이 문장만 있으면 두 번 같은 파일를 실행 파일에 인클루드하는 일은 없겠군요. 이미 _STDIO_H_가 정의 되어 있으면 조건문 밖으로 빠져 나갈 테니까요!
## 제갈량의 동남풍 : stdio.h가 궁금하다
안녕하세요? 제갈량입니다.
그동안 저와 함께 수도 없이 stdio.h를 인클루드했는데요, stdio.h가 어떻게 생겼는지 다들 궁금하시리라 봅니다.
다음은 stdio.h의 일부입니다.
```
#ifndef _STDIO_H_
#ifdef __cplusplus // C++ 컴파일러면
extern "C" { // C 언어 파일이므로 임포트한 라이브러리의 심볼 형식을
// 이제부터 C 스타일로 지정
#endif
#define _STDIO_H_
.
.
.
// 중략(라이브러리로 사용할 함수 선언 등)
.
.
.
#ifdef __cplusplus
}
#endif
#endif /* _STDIO_H_ */
```
딱보니 중복 인클루드는 방지하는 코드가 보이는군요.
* ① #ifndef _STDIO_H_
* ② #ifdef __cplusplus : C++ 컴파일러면
* ③ extern "C" { : C 언어 파일이므로 임포트한 라이브러리의 심볼 형식을 이제부터 C 스타일로 지정
* ④ #endif : ②부터 ③까지 코드 블록을 닫습니다.
* ⑤ #define _STDIO_H_ : 드디어 중복 인클루드 방지용 실별자를 지정합니다.
* ⑥ 라이브러리로 사용할 함수 선언이 있습니다.
* ⑦ #ifdef __cplusplus : C++ 컴파일러면
* ⑧ } : ③에서 연 extern "C" 형식을 드디어 닫습니다.
* ⑨ #endif : ⑦ ~ ⑧ 코드 블록을 닫습니다.
* ⑩ #endif /* _STDIO_H_ */ : ①을 닫습니다.
실제로 stdio.h 파일을 열어보면 아주 복잡해 보일 겁니다. 그렇지만 이렇게 구조를 파악하고 나면 파악이 용이하죠.
분석에 관심있는 분은 다음 코드를 살펴보세요!
* [https://www.gnu.org/software/m68hc11/examples/stdio_8h-source.html](https://www.gnu.org/software/m68hc11/examples/stdio_8h-source.html)
참고로 __cplusplus는 C++에서 제공하는 매크로입니다. 이 매크로 식별자가 있다면 C 컴파일러가 아닌 거죠.
매크로
|
설명
|
자료형
|
__cplusplus
|
C++ 컴파일러 식별자 / C 언어 내장 매크로 아님(C++ 지시어)
|
문자열
|
이상 제갈량이었습니다.
**風**
|
파일명> |