핵심 정리
매크로 정의하기
#define은 특정 값에 이름을 붙이거나 코드를 조합하여 함수 형태로 만들 수 있습니다. 매크로를 사용하면 전처리기를 거쳐 내부적으로 소스 코드가 일괄 변환됩니다.
// 값을 매크로로 정의 // 10을 COUNT로 정의 #define 매크로이름 값 #define COUNT 10 // 여러 줄을 매크로로 정의 // printf 세 줄을 PRINT_NUM3으로 정의 #define 매크로이름 코드1 \ #define PRINT_NUM3(x) printf("%d\n", x); \ 코드2 \ printf("%d\n", x + 1); \ 코드3 printf("%d\n", x + 2); // 값을 붙이는 매크로 정의 // a와 b를 붙이는 CONCAT 매크로 정의 #define 매크로이름(a, b) a##b #define CONCAT(a, b) a##b
함수 모양의 매크로 정의하기
매크로를 정의할 때 괄호 안에 인수 이름을 지정하면 함수 모양의 매크로를 정의할 수 있습니다. 특히 do while (0)을 사용하면 매크로 안에서 변수를 선언할 수 있고 if, else에서 컴파일 에러를 방지할 수 있습니다.
// 함수 모양으로 매크로 정의 // 숫자를 출력하는 PRINT_NUM 매크로 정의 #define 매크로이름(x) 함수(x) #define PRINT_NUM(x) printf("%d\n", x) #define 매크로이름(x) 코드조합 // do while (0)을 이용하여 매크로 안에서 변수 선언. // if, else에서 컴파일 에러 방지 #define SWAP(a, b, type) do { \ type temp; \ temp = a; \ a = b; \ b = temp; \ } while (0)
매크로 해제하기
#undef는 정의한 매크로를 해제합니다.
// 정의한 매크로 해제 // COUNT 매크로 해제 #undef 매크로 #undef COUNT
매크로와 연산자 우선순위
매크로를 정의할 때는 연산자 우선순위에 영향을 받지 않도록 인수와 결과를 괄호로 묶어주어야 합니다. 괄호로 묶어주지 않으면 우선순위가 높은 연산자가 먼저 계산됩니다.
#define MUL(a, b) a * b #define ADD(a, b) a + b // 결과로 21을 예상함 printf("%d\n", MUL(1 + 2, 3 + 4)); // 11: 1 + 2 * 3 + 4 // 1 + 6 + 4 // 11 // 2 * 3이 먼저 계산됨 // 결과로 9를 예상함 printf("%d\n", ADD(1, 2) * 3); // 7: 1 + 2 * 3 // 1 + 6 // 7 // 2 * 3이 먼저 계산됨
괄호로 묶어주면 연산자 우선순위에 영향을 받지 않습니다.
#define MUL(a, b) ((a) * (b)) #define ADD(a, b) ((a) + (b)) printf("%d\n", MUL(1 + 2, 3 + 4)); // 21: ((1 + 2) * (3 + 4)) // ((3) * (7)) // (21) printf("%d\n", ADD(1, 2) * 3); // 9: ((1) + (2)) * 3 // (3) * 3 // 9
조건부 컴파일
조건부 컴파일은 특정 조건에 따라 컴파일 여부를 결정합니다.
// 매크로가 정의되어 있을 때 컴파일 // DEBUG 매크로가 정의되어 있을 때 컴파일 #ifdef 매크로 #ifdef DEBUG 코드 printf("Debug\n"); #endif #endif // 매크로가 정의되어 있지 않을 때 컴파일 // DEBUG 매크로가 정의되어 있지 않을 때 컴파일 #ifndef 매크로 #ifndef DEBUG 코드 printf("Hello, world!\n"); #endif #endif // 값 또는 식이 참일 때 컴파일 // DEBUG_LEVEL이 2 이상일 때 컴파일 #if 값 또는 식 #if DEBUG_LEVEL >= 2 코드 printf("Debug Level 2\n"); #endif #endif // 조건이 항상 거짓이므로 컴파일 하지 않음 #if 0 printf("0\n"); #endif // 조건이 항상 참이므로 컴파일함 #if 1 printf("1\n"); #endif // 매크로가 정의되어 있을 때 컴파일. // !, &&, ||로 논리 연산 가능 // DEBUG 또는 TEST가 정의되어 있을 때 컴파일 #if defined 매크로 #if defined DEBUG || defined TEST 코드 printf("Debug\n"); #endif #endif // DEBUG가 정의되어 있으면서 // VERSION_10이 정의되어 있지 않을 때 컴파일 #if defined (DEBUG) && !defined (VERSION_10) printf("Debug\n"); #endif // if, elif, else로 조건부 컴파일 // DEBUG_LEVEL의 값에 따라 컴파일 #if 조건식 #if DEBUG_LEVEL == 1 코드 printf("Debug Level 1\n"); #elif 조건식 #elif DEBUG_LEVEL == 2 코드 printf("Debug Level 2\n"); #else #else 코드 printf("Hello, world!\n"); #endif #endif // if, elif, else로 조건부 컴파일 // PS2, USB 정의 여부에 따라 컴파일 #ifdef 매크로 #ifdef PS2 코드 printf("PS2\n"); #elif defined 매크로 #elif defined USB 코드 printf("USB\n"); #else #else 코드 printf("지원하지 않는 장치입니다.\n"); #endif #endif
파일 포함하기
#include를 사용하면 다른 파일을 포함합니다. 또한, #include는 조건부 컴파일과 함께 사용하여 파일 포함 여부를 결정할 수 있습니다.
#include <파일> // C 언어 표준 라이브러리의 헤더 파일을 포함할 때 사용 // 컴파일 옵션에서 지정한 헤더 파일 경로를 기준으로 파일을 포함 #include "파일" // 현재 소스 파일을 기준으로 헤더 파일을 포함 // 헤더 파일을 찾지 못할 경우 컴파일 옵션에서 지정한 헤더 파일 경로를 따름
파일을 포함하기 전에 매크로를 정의하면 포함된 파일 안에도 매크로의 내용이 적용됩니다.
헤더 파일을 여러 소스 파일에서 사용할 때 컴파일 에러 방지
같은 헤더 파일을 여러 소스 파일에서 사용하면 구조체 정의 부분에서 에러가 발생합니다. 따라서 다음과 같이 매크로가 정의되어 있지 않으면 컴파일을 하고, 정의되어 있으면 컴파일을 하지 않는 방식을 사용해야 합니다.
#ifndef 매크로A // 매크로A가 정의되어 있지 않다면 #ifndef DATA_H // DATA_H가 정의되어 있지 않다면 #define 매크로A // 매크로A 매크로 정의 #define DATA_H // DATA_H 정의 // 구조체, 공용체, 열거형 등 정의 typedef struct _DATA { // 구조체 _DATA 정의 int a; #endif // #ifndef 매크로A 끝 int b; } DATA; #endif // #ifndef DATA_H 끝