목차
- 전처리기란?
- 파일을 포함하는 #include
- 매크로를 정의하는 #define
- 조건부 컴파일 지시자 #ifdef, #endif
- 조건부 컴파일 지시자 #if, #else
- 다중 소스 파일
- 비트 필드 구조체
1. 전처리기란?
이거 2-1장에서 나왔던건데 이제야 배우는군요..
전처리기(peprocessor)는 컴파일하기 앞서서 소스 파일을 처리하는 컴파일러의 한 부분입니다.
이 부분은 #define, #include 등만 처리해요.
전처리기의 지시어는 다음과 같은 부분이 존재합니다.
지시어 | 의미 |
#define | 매크로 정의 |
#include | 파일 포함 |
#undef | 매크로 정의 해제 |
#if | 조건이 참일 경우 |
#else | 조건이 거짓일 경우 |
#endif | 조건 처리문 종료 |
#ifdef | 매크로가 정의되어 있는 경우 |
#ifndef | 매크로가 정의되어 있지 않은 경우 |
#line | 행번호 출력 |
#pragma | 컴파일 세부 제어(시스템에 따라 다름) |
2. 파일을 포함하는 #include
#include는 지정한 파일의 내용을 읽어와 지시자가 있는 위치에 붙여놓습니다.
사용법은 헤더 파일을 포함할 때처럼
#include <stdio.h> 을 선언하거나, 또는 " "로 묶을 수 있습니다.
한번 해볼까요..
#include <stdio.h>
#include "student.h" // student 구조체 정의가 포함된 헤더 파일
int main(void)
{
// student 구조체를 사용하여 학생 변수 a를 선언하고 초기화합니다.
student a = { 20250001, "홍길동" };
printf("학번 : %d\n이름 : %s", a.num, a.name);
return 0;
}
student.h는 VSC의 헤더 파일 부분에 넣어두세요!
// student 구조체 typedef 선언
// 이 구조체는 학생의 학번(num)과 이름(name)을 저장합니다.
typedef struct
{
int num; // 학생의 학번 (정수형)
char name[20]; // 학생의 이름 (최대 19자 + 널 종료 문자)
} student;
결과는 다음과 같습니다.
학번 : 20250001
이름 : 홍길동
< > 와 " " 의 차이는 간단합니다. <>은 C 컴파일러가 보유하고 있는 표준 라이브러리 함수를 끌고오고,
" "은 사용자가 직접 만든 헤더 파일을 의미합니다.
실제로 전처리기 실행 후에는 코드들이 그냥 복사되는겁니다. main 파일 안에요.
stdio.h라던가, 지금 집어넣은 student.h 파일들이 main 파일 안으로 들어가서 컴파일을 대기하게 됩니다.
다만 이런 경우 중복 문제가 생기는데, 이를 위해 매크로 정의를 관리하는 전처리기 지시문을 적절하게 사용해야 합니다.
3. 매크로를 정의하는 #define
매크로는 단순 매크로와 함수 매크로로 나눠집니다.
먼저 단순 매크로부터 알아봅시다.
단순 매크로는
#define 매크로명 치환될_부분
으로 구성됩니다.
예를 들어 #define PI 3.14라고 정의하면,
변수 PI를 입력하면 자동으로 전처리기가 이를 3.14로 치환합니다.
이것 말고도 " "을 이용해 문자열을 정의할 수도 있고, 출력 또한 정의할 수 있습니다.
#define MSG "passed!"
#define ERR printf("출력 오류입니다.")
처럼 말이죠.
#include <stdio.h>
// 단순 매크로 정의
#define PI 3.14 // 원주율을 정의합니다.
#define MSG "passed!" // 메시지 문자열을 정의합니다.
#define ERR printf("출력 오류입니다.\n") // 오류 출력 구문을 정의합니다.
int main(void)
{
double radius = 5.0;
// PI 매크로를 사용하여 원의 넓이를 계산합니다.
double area = PI * radius * radius;
printf("원의 넓이: %.2f\n", area);
printf("시험 결과: %s\n", MSG);
// 임의의 조건이 false일 경우, ERR 매크로를 사용하여 오류 메시지를 출력합니다.
int condition = 0;
if (!condition)
{
ERR; // 전처리 단계에서 printf("출력 오류입니다.\n"); 로 치환됩니다.
}
return 0;
}
출력은 다음과 같습니다.
원의 넓이: 78.50
시험 결과: passed!
출력 오류입니다.
이처럼 단순 매크로를 사용하면 프로그램의 가독성을 높이고, 상수를 쉽게 변경할 수 있다는 장점이 있습니다.
이런 간단한 작업 말고 복잡한 작업을 담당하는 매크로 함수도 만들 수 있습니다.
가령 제곱을 담당하는 함수를 하나 만들어볼까요..
#include <stdio.h>
// SQUARE 매크로 정의
// 전달받은 값 x에 대해 제곱을 계산하는 매크로입니다.
// 주의: 괄호를 충분히 사용하지 않으면, 복합 표현식에서 예기치 않은 결과가 발생할 수 있습니다.
// 예: #define SQUARE(x) ((x)*(x)) 로 사용하는 것이 안전하지만, 여기서는 예시를 위해 (x)*(x) 로 정의했습니다.
#define SQUARE(x) (x)*(x)
int main(void)
{
int sq; // 정수형 변수 sq: SQUARE 매크로를 통해 계산된 결과를 저장합니다.
// SQUARE 매크로를 사용하여 숫자 12의 제곱을 계산합니다.
sq = SQUARE(12);
printf("12^2 = %d\n", sq);
return 0;
}
출력은 다음과 같습니다.
12^2 = 144
이러한 함수 매크로에는 몇 가지 장점이 있습니다.
함수 호출 단계가 필요없어서 실행 속도가 빨라지고, 소스코드의 길이가 줄어듭니다.
간단한 함수 기능들은 이 매크로를 사용해서 만드는 것이 좋습니다.
매크로를가 너무 길어져서 보기 별로라면 어떻게 해야 할까요?
함수 내에서는 그냥 다음줄로 내려버리면 됩니다. 문장의 끝은 세미콜론 ; 이니까요.
하지만 함수 밖에서 전처리기를 지시할 때는 끝에 역슬래시 \ 을 붙이고 다음 줄로 내려야 합니다.
다만, 역슬래시에 무언가가 붙어있으면 안됩니다.
그리고 당연하게도 주의점이 있습니다.
기본적인 함수의 주이점인 모든 매개변수를 사용해야 한다는 점과, 매크로 이름과 괄호 사이에 공백이 있으면 안된다는 것,
그리고 주석에도 있습니다만, 복합 표현식에서는 괄호를 많이 사용해야 합니다.
그렇지 않으면 예상하지 못한 결과가 나올 수 있습니다.
가령 저 경우, SQUARE(++n)를 하면(n=2) SQUATE가 x*x를 4*4로 계산해서 16이 반환됩니다.
즉, (++n)*(++n)으로 계산되어서.. n*(++n) , 이때 n =3이고, n*n (n=4가 됨)
이러한 매크로들 말고도, C가 미리 정의해둔 내장 매크로도 존재합니다.
내장 매크로 | 기능 |
__DATE__ | 컴파일을 시작한 날짜 |
__TIME__ | 컴파일을 시작한 시간 |
__LINE__ | 매크로가 사용된 행 번호 |
__FILE__ | 전체 디렉터리 경로를 포함한 파일명 |
#include <stdio.h>
int main(void)
{
/*
* __FILE__은 C 표준 전처리기 매크로로,
* 해당 소스 파일의 이름(또는 경로 포함 이름)을 문자열로 나타냅니다.
* 따라서 아래 printf 함수는 컴파일 되는 파일의 이름을 출력합니다.
*/
printf("%s", __FILE__);
return 0;
}
출력은 다음과 같습니다.
E:\project\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.c
4. 조건부 컴파일 지시자 #ifdef, #endif
조건부 컴파일은 소스 코드를 조건에 따라 선택적으로 컴파일합니다.
이때 여러가지 전처리 지시자를 사용하는데, 한번 사용해봅시다.
#include <stdio.h>
// ALPHA와 BETA 매크로를 정의합니다.
// 이 매크로들이 정의되어 있으므로, 아래 조건부 컴파일에서 해당 블록들이 활성화됩니다.
#define ALPHA
// #define BETA , 주석으로 제거 처리됨
int main(void)
{
// 만약 ALPHA 매크로가 정의되어 있다면, 아래 코드를 컴파일에 포함시킵니다.
#ifdef ALPHA
printf("ALPHA 버전입니다.\n");
#endif
// 만약 BETA 매크로가 정의되어 있다면, 아래 코드를 컴파일에 포함시킵니다.
#ifdef BETA
printf("BETA 버전입니다.\n");
#endif
return 0;
}
출력은 다음과 같습니다.
ALPHA 버전입니다.
컴파일 과정에서 ifdef에 해당하지 않는 부분은 컴파일되지 않습니다.
이러한 조건부 컴파일 지시자를 통해 프로그램의 다른 버전을 쉽게 관리할 수 있습니다.
지금 같은 경우는 ALPHA, BETA가 ifdef로 정의되어 있지만, 이 경우에는 else를 통해서도 작업이 가능합니다.
5. 조건부 컴파일 지시자 #if, #else
if는 기호가 참으로 계산되면 컴파일합니다.
else는 조건이 아닐 경우 컴파일합니다.
#include <stdio.h>
// ALPHA 매크로를 값 1로 정의합니다.
// 주석 처리된 #define BETA는 실제로 정의되지 않으므로, BETA 관련 코드는 조건에 따라 컴파일되지 않습니다.
#define ALPHA 1
// #define BETA 2 // 이 줄은 주석 처리되어 있어 BETA는 정의되지 않습니다.
int main(void)
{
/*
* #if 전처리 지시자는 뒤에 오는 식을 평가합니다.
* 여기서 ALPHA가 1로 정의되어 있으므로, ALPHA는 1로 치환되어
* #if 1, 즉 0이 아닌 값(참)으로 평가됩니다.
*/
#if ALPHA
// ALPHA가 참(0이 아닌 값)이므로 이 블록이 컴파일됩니다.
printf("ALPHA 버전입니다.\n");
#else
// ALPHA가 참이면 이 부분은 무시됩니다.
printf("BETA 버전입니다.\n");
#endif
return 0;
}
#if는 후속 계산을 수치 표현(정수 상수)으로만 평가합니다.
따라서 단순히 매크로의 존재 여부만 확인하려면 #ifdef를 사용하는 것이 좋으며,
#if는 몇 가지 복합적인 관계연산 또는 수치 기반 조건을 시행할 때 사용됩니다(버전, 조건문 등)
6. 다중 소스 파일
지금까지 거의 대부분의 프로그래밍은 main문이 들어가 있는 소스 파일 하나만 굴려왔습니다.
이를 단일 소스 파일이라고 합니다.
이 경우 파일의 크기가 커지고, 사용된 소스 파일들을 재사용하기 어렵습니다.
이럴 때 사용하는 것이 다중 소스 파일입니다.
여러개의 소스파일을 연결시키고, 컴파일러를 통해 하나의 실행 파일로 통합하는 것입니다.
이 경우 소스파일의 재사용이 간편해지고, 가독성도 높아진다는 장점이 있습니다.
사실 include를 배우는 과정에서 한번 써봤어요.
한번 거듭제곱을 구하는 함수 power을 만들고 이걸로 다중 소스 파일을 만들어 봅시다.
#define _CRT_SECURE_NO_WARNINGS
#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제곱은 %d입니다.\n", x, y, power(x, y));
return 0;
}
int power(int x, int y)
{
int result = 1;
for (int i = 0; i < y; i++)
result *= x;
return result;
}
출력 결과는 다음과 같습니다.
x의 값을 입력하시오 : 4
y의 값을 입력하시오 : 4
4의 4제곱은 256입니다.
지금 같은 경우에는 헤더파일을 따로 사용하지 않았지만, 실제 프로그래밍에서는 헤더 파일에 함수 정의를 모아두는 방법을 사용합니다.
7. 비트 필드 구조체
비트 필드 구조체는 멤버가 비트 단위로 나누어져 있는 구조체를 말합니다.
기본적으로 C의 정수형 변수는 4바이트 단위로 저장되지만, 비트필드를 사용하면 필요한 만큼의 비트만 할당할 수 있어서 메모리를 더 효율적으로 사용할 수 있습니다.
예를 들어 불(bool)자료형이나, 작은 숫자를 저장하기 위해 적은 비트가 필요하다면 정수형 변수의 전체 크기를 할당할 필요 없이 필요한 만큼 작은 비트만 저장할 수 있습니다.
'프로그래밍 > C' 카테고리의 다른 글
변수의 영역과 데이터 공유 - 예제 모음 (0) | 2025.06.17 |
---|---|
동적 할당 메모리 (0) | 2025.06.14 |
파일 입출력 함수 (0) | 2025.06.14 |
파일 개방과 입출력 (2) | 2025.06.14 |
함수와 특수 타입 (0) | 2025.06.14 |