과제 - 1
- A_MAX, B_MAX, C_MAX 중 한 개의 값이 필요한 경우 해당 값이 정의된 헤더파일만 include 합니다.
- B_MAX, C_MAX 둘다 필요한 경우 header_b.h, header_c.h 둘다 include해서 컴파일이 되어야합니다.
/* header_a.h */
#ifndef HEADER_A_H //헤더파일A가 여러번 포함될 예정이기 때문에, 헤더가드를 작성합니다.
#define HEADER_A_H
#define A_MAX 10
#endif
/* header_b.h */
#include "header_a.h"
#define B_MAX A_MAX+2
/* header_c.h */
#include "header_a.h"
#define C_MAX A_MAX+1
/* main.c */
#include <stdio.h>
#include "header_a.h"
#include "header_b.h"
#include "header_c.h"
int main()
{
printf("A_MAX = %d\n", A_MAX); //10
printf("B_MAX = %d\n", B_MAX); //12
printf("C_MAX = %d\n", C_MAX); //11
return 0;
}
과제 - 2
- 2-4 처리 중간 결과에 대한 디버깅을 위한 디버깅 코드 추가하기 (내부 변수 내용 출력)
- 컴파일 옵션으로 디버깅 on/off 기능 추가하기
void Change(char* dest, char* src)
{
...
#if DEBUG=1 (#if DEBUG와 동일함)
printf("%c ", *dest);
#endif
...
}
#define
- 매크로를 정의할 때 사용하는 키워드
- 컴파일 과정인 전처리기->컴파일러->어셈블러->링커 중 전처리기 단계에서 #define으로 정의된 기호 상수(기호를 이용하여 상수를 표기) 등을 소스코드로 대체시킵니다.
- #define은 단순히 치환되는 형태이기 때문에, 매크로 함수를 호출할 때 인자 타입이 맞지 않아도 에러가 나지 않습니다.
- 매크로 인자가 상수면, 컴파일 시점에 상수로 사용할 수 있습니다.
- 매크로 인자가 변수면, 컴파일 시점에 상수로 사용할 수 없습니다.
- 매크로 상수. 미리 정의한 매크로 상수명이 사용되면, 매크로 확장 문자열로 치환됩니다.
- 매크로 함수. 매크로 상수와는 달리, 매크로 함수 이름에 괄호와 함께 인자 목록이 주어져있지만 단순히 치환하기만 하므로 실제 함수는 아닙니다. 인자로 받는 변수의 타입을 신경 쓰지않아 타입을 상관하지 않는 type-generic 함수를 구현하고자 할 때 매크로를 사용합니다.
- 예제.
#define MUL(X,Y) ((X)*(Y))
#define PRINT(s) printf(#s) //# 문자열로 변환시키는 연산자
#define CONCAT(X,Y,Z) X ## Y ## Z //## 두 개의 토큰을 이어주는 연산자
#define TEMP(X) hello ## X() //hello에 단어(문자,숫자)를 이어 호출하는 매크로
void hello1()
{
PRINT(hello);
}
int main()
{
int num = CONCAT(1,2,3);
printf("%d\n", num); //123 숫자 출력
TEMP(1); //hello1 함수 호출
return 0;
}
#undef
- 정의된 매크로를 해제할 때 사용하는 키워드
- #ifndef와도 연동 가능
#include <파일명.h> vs #include "파일명.h"
- 해당 헤더파일을 검색하는 순서의 차이
#include <파일명.h>
(라이브러리에 정의된 헤더파일을 포함할 때) 컴파일러의 라이브러리 폴더를 검색합니다.
#include "파일명.h"
현재 소스가 존재하는 폴더를 처음에 검색하고, 찾는 헤더파일이 없을때 라이브러리 폴더를 검색합니다. 주로 사용자가 정의한 헤더파일을 포함할때 사용됩니다.
#include
는 헤더파일이 아닌 cpp파일에 하는 것이 퍼포먼스가 더 좋습니다. 대용량 프로그램 작성시 컴파일 속도에서 많은 차이가 나타납니다.
#ifdef/#endif
#ifdef A //A가 사전에 정의되었다면 문장a를 컴파일 합니다.
문장a
#endif
#ifndef/#endif
#ifndef B //B가 사전에 정의되지 않았다면 문장b를 컴파일 합니다.
문장b
#endif
#if/#elif/#endif
#define A 0
#if A //A에 들어가는 값이 중요하게 작용됩니다.
문장a
#elif num==B //조건을 계속 분기할 수 있는 else if와 같은 역할.
문장b
#endif
#error
#error 메시지
- 전처리기가 해당 메시지를 발견하면 컴파일이 중지되고 여기에 포함된 오류 메시지를 전달합니다.
void hello1()
{
#error This is an error.
}
//오류 발생
str.c: In function ‘hello1’:
str.c:11:2: error: #error This is an error.
11 | #error This is an error.
| ^~~~~
make: *** [<builtin>: str.o] Error 1
조건부 컴파일
if~else
문과 달리 #if~#elif~#endif
또는 #ifdef~#endif
를 사용하게 되면, 조건부 컴파일이 가능합니다.
- 조건을 쓸 때 주의할 점은, 조건에 0,1과 같은 상수는 사용할 수 있지만 변수를 사용하면 인식하지 못합니다. 따라서 매크로를 사용해야 합니다.
- 디버깅과 관련해서 자주 사용되는 문법으로, 디버깅 시에는 특정 문자들을 수행하여 결과값을 출력해보는 문장을 넣어두고 컴파일 합니다. (프로그램의 동작상태를 런타임에 확인하는 용도일뿐, 실제 프로그램의 기능에는 영향을 미치지 않습니다.)
#define DEBUG 1 or 0
#if DEBUG==1
printf("현재 num의 값은 %d입니다.\n", num);
#endif
#ifdef DEBUG
printf("현재 num의 값은 %d입니다.\n", num);
#endif
헤더파일의 중복방지
- 같은 헤더파일이 여러번 포함된 경우 문제가 생기기에, 헤더파일에 "이미 포함되었다"는 것을 의미하는 #define을 추가해야 합니다. (헤더가드, 인클루드가드)
#ifndef HEADER_H
#define HEADER_H
/*...contents of <header.h>...*/
#endif /* HEADER_H */