쉽게 풀어쓴 C언어 Express 제15장 전처리 및 비트 필드 C Express
이번 장에서 학습할 내용 전처리와 기타 중요한 테마에 대하여 학습한다. 전처리 지시자 다중 소스 프로그램 비트 필드
전처리기란? 전처리기(preprocessor): 컴파일에 앞서 소스 파일을 처리 수고했어, 나머지는 나한테 맡겨! #include, #define 등을 처리 합니다. 소스 파일 임시 파일 오브젝트 파일 전처리기 컴파일러
단순 매크로 단순 매크로(macro): 기호 상수 정의 전처리기 #define SIZE 100 사람은 숫자보다 기호를 잘 기억 하므로 3.141592 보다는 PI가 낫죠! #define SIZE 100 #define PI 3.141592 #define SIZE 100 while (i < SIZE) { sum += i; i++; } while (i < 100) { sum += i; i++; } 전처리기
단순 매크로의 장점 프로그램의 가독성 향상 상수 변경이 용이 리터럴을 사용하는 경우: 모든 곳을 수정해야 함 기호 상수를 사용하는 경우: 정의된 부분만 수정하면 됨
단순 매크로의 예 #define SIZE 100 // 배열 크기 #define MAX_INT 2147483647 // 최대정수 #define PI 3.141592 // 원주율 #define TWOPI (3.141592 * 2.0) // 원주율의 2배 #define EOF (-1) // 파일의 끝 표시 #define DIGITS "0123456789" // 문자열 상수 정의 #define BRACKET "(){}[]" // 문자열 상수 정의 #define MAX_LEN 100 // 문자열 최대 길이 #define MAX_SIZE (MAX_LEN + 1) // 문자 배열 최대 크기 #define EXPR x = y + 1; p = "abc" // 어떤 형태라도 정의 가능
함수형 매크로 함수형 매크로: 함수처럼 인자를 가질 수 있는 매크로 #define SQUARE(x) ((x) * (x)) v = SQUARE(3); v = ((3) * (3)); #define SUM(x, y) ( (x) + (y) ) #define AVERAGE(x, y, z) ( ( (x) + (y) + (z) ) / 3 ) #define MAX(x, y) ( (x) > (y) ) ? (x) : (y) ) #define ABS(x) ( (x) < 0 ) ? -(x) : (x) ) #define getchar() getc(stdin) #define putchar(c) putc((c), stdout)
주의할 점 #define SQUARE(x) x * x // 위험 !! v = SQUARE(1 + 2); 함수형 매크로에서는 인자와 전체를 괄호로 둘러싸는 것이 안전합니다.
함수형 매크로의 장단점 함수 호출보다 실행 속도가 빠름 큰 매크로를 많이 사용하면 실행 프로그램 크기 증가 디버깅이 어려움 함수에 비해 위험 #define SQUARE(x) ((x) * (x)) n = 1; v = SQUARE(++n); // v = ((++n) * (++n)); // n = 3, v = 9 or 6 int square(int x) { return x * x; } n = 1; v = square(++n); // n = 2, v = 4
예제 #include <stdio.h> #define SQUARE(x) ((x) * (x)) int main(void) { int x = 2; printf("%d\n", SQUARE(x)); printf("%d\n", SQUARE(3)); printf("%f\n", SQUARE(1.2)); printf("%d\n", SQUARE(x+3)); printf("%d\n", 100/SQUARE(x)); printf("%d\n", SQUARE(++x)); // 논리 오류 return 0; } 4 9 1.440000 25 16
매크로 정의 해제 #undef 매크로: 매크로에 대한 정의 취소(undefine) #define SIZE 10 … #undef SIZE int SIZE; 긴 매크로 처리 방법: 한 줄 이상이면 반드시 끝에 연속 마크(\) 사용 #define OUT_ERROR(code, message) \ printf("[%d] %s\n", \ (code), (message))
매크로 연산자 # 연산자: 매크로 인자를 문자열로 변환 (전위 단항 연산자) #define PRINT(exp) printf(#exp " = %d\n", (exp)) x = 1; PRINT(x + 2); // printf("x + 2" " = %d\n", (x + 2)); // printf("x + 2 = %d\n", (x + 2)); // x + 2 = 3 ## 연산자: 하나의 토큰으로 병합 (이항 연산자) #define VAR(n) v##n #define TOKEN(name, n) name ## n VAR(1) = 2; // v1 = 2; s = TOKEN(sum, 2); // s = sum2;
예제 #include <stdio.h> #define MAKE_NAME(n) v ## n #define PRINT(n) printf("v" #n " = %d\n", MAKE_NAME(n)); int main(void) { int MAKE_NAME(1) = 10; // int v1 = 10; int MAKE_NAME(2) = 20; // int v2 = 20; PRINT(1); // printf("v1 = %d\n", v1); PRINT(2); // printf("v2 = %d\n", v2); return 0; } v1 = 10 v2 = 20
내장 매크로 내장 매크로: 미리 정의된 매크로 매크로 값 __DATE__ 컴파일 날짜(문자열) __TIME__ 컴파일 시간(문자열) __FILE__ 소스 파일 이름(문자열) __LINE__ 현재 라인 번호(정수) printf("컴파일 시간 = %s %s\n", __DATE__, __TIME__); // 컴파일 시간 = Oct 9 2012 13:03:04 printf("에러 발생: 파일 이름 = %s, 라인 번호 = %d\n", __FILE__, __LINE__);
예제: ASSERT 매크로 #include <stdio.h> #include <stdlib.h> #define ASSERT(exp) \ if (!(exp)) \ printf("[%s %d] 조건 실패: " #exp "\n", __FILE__, __LINE__), exit(1) int main(void) { int sum; ASSERT(sum == 0); return 0; } [assert.c 10] 조건 실패: sum == 0
비트 조작 매크로 #define BIT_GET(w, k) (((w) >> (k)) & 0x01) 결과: 0 / 1 #define BIT_SET (w, k) ((w) | 0x01 << (k)) w의 k번째 비트를 1로 설정 #define BIT_RESET(w, k) ((w) & ~(0x01 << (k))) w의 k번째 비트를 0로 설정
예제: 비트 조작 매크로 #include <stdio.h> #define BIT_GET(w, k) (((w) >> (k)) & 0x01) #define BIT_SET(w, k) ((w) | 0x01 << (k)) #define BIT_RESET(w, k) ((w) & ~(0x01 << (k))) int main(void) { int data = 0; data = BIT_SET(data, 2); printf("%08X %d\n", data, BIT_GET(data, 2)); data = BIT_RESET(data, 2); return 0; } 00000004 1 00000000 0
조건부 컴파일 지시자 #if 조건: 조건이 참이면 조건: 정수 상수 조건이어야 하고 논리, 관계 연산자 등 허용 #ifdef 매크로: 매크로가 정의되어 있으면 #ifdef M #if defined(M) defined(M): 전처리 연산자, 매크로 M이 정의되어 있으면 참 #ifndef 매크로: 매크로가 정의되어 있지 않으면 #ifndef M #if !defined(M) #else: 그렇지 않으면 #elif 조건: 그렇지 않고 조건이 참이면 (else if) #endif: 조건부 컴파일 지시자 끝 #if / #ifdef / #ifndef … #endif
조건부 컴파일 지시자 예 #if DEBUG_LEVEL >= 1 printf("result = %d\n", result); #endif #ifdef LINUX ... // LINUX 버전인 경우 #else ... // LINUX 버전이 아닌 경우 #endif #if NATION == 1 #include "korea.h" #elif NATION == 2 #include "china.h" #else #include "usa.h" #endif #ifndef LIMIT // LIMIT이 정의되어 있지 않으면 # define LIMIT 1000 #endif #if 0 // 전처리 기능을 이용한 주석 처리 … #endif
예제 #include <stdio.h> #define DELUXE int main(void) { #ifdef DELUXE printf("딜럭스 버전입니다.\n"); #endif return 0; } 딜럭스 버전입니다.
조건부 컴파일 지시자 #if VERSION > 3 // 가능! 버전이 3보다 크면 #if (AUTHOR == KIM) // 가능! KIM은 매크로 #if (VERSION * 10 > 500 && LEVEL == BASIC) // 가능! #if (VERSION > 3.0) // 오류! 실수는 허용되지 않음 #if (AUTHOR == "CHULSOO") // 오류! 문자열은 허용되지 않음 #if (VERSION > 300 || defined(DELUXE)) // 가능!
#include #include <…> 표준 디렉토리(폴더)에서 파일 탐색 #include "…" 현재 디렉토리에서 파일 탐색 표준 디렉토리에서 파일 탐색 (현재 디렉토리에 없는 경우) 경로 지정 가능 #include "graphic/point.h" #include "D:\Project\source\point.h" #include "/home/user1/source/point.h"
헤더 파일 중복 포함 방지 // rect.h #ifndef RECT_H #define RECT_H // rect.h struct rect { int x, y, w, h; }; … #endif // rect.h struct rect { int x, y, w, h; }; … // shape.h #include "rect.h" … // shape.h #ifndef SHAPE_H #define SHAPE_H #include "rect.h" … #endif // main.c #include <stdio.h> #include "rect.h" #include "shape.h" …
다중 소스 파일 단일 소스 파일 다중 소스 파일 소스의 재사용과 관리가 어려움 일부만 수정한 경우에도 전체를 다시 컴파일해야 함 다중 소스 파일 모듈별로 별도의 소스 파일 유지 소스의 재사용과 관리가 용이함 변경된 소스 파일만 다시 컴파일 컴파일 시간 단축
다중 소스 파일 라이브러리
예제 power.h main.c #ifndef POWER_H #define POWER_H double power(int x, int y); #endif #include <stdio.h> #include "power.h" int main(void) { int x,y; printf("x의 값을 입력하시오: "); scanf("%d", &x); printf("y의 값을 입력하시오: "); scanf("%d", &y); printf("%d의 %d 제곱값은 %f\n", x, y, power(x, y)); return 0; } power.c #include "power.h" double power(int x, int y) { double result = 1.0; int i; for (i = 0;i < y; i++) result *= x; return result; } x의 값을 입력하시오: 2 y의 값을 입력하시오: 3 2의 3 제곱값은 8.000000
헤더 파일을 사용하지 않으면 함수 원형 선언 반복
헤더 파일을 사용하면 헤더 파일 포함
다중 소스 파일에서 외부 변수
비트 필드 int/unsigned 타입의 구조체 필드는 비트 단위로 크기 지정 가능 비트 필드 메모리 절약 struct product { unsigned style : 1; // [0, 1] unsigned size : 2; // [0, 3] int value : 5; // [-16, +15] }; int/unsigned (word) value size style
예제 ? … 1 #include <stdio.h> struct product { unsigned style : 1; unsigned size : 2; int value : 5; }; int main(void) { struct product p; p.style = 1; p.size = 2; p.value = -10; printf("style = %u, size = %u, value = %d\n", p.style, p.size, p.value); printf("sizeof p = %d\n", sizeof p); printf("p = %08X\n", p); return 0; } style = 1, size = 2, value = -10 sizeof p = 4 p = 000000B5 ? … 1 value size style
비트 필드 code struct product { int code; // 일반 멤버 unsigned style : 1; unsigned size : 2; int value : 5; unsigned : 0; // 현재 워드의 나머지 비트는 사용하지 않음 unsigned state : 4; // 다음 워드에 할당 }; code value size style state