전처리 지시자
소스파일→ 전처리된 소스파일 → 목적파일 → 실행파일
전처리기가 소프코드를 컴파일하기 좋게 다듬는 과정.
#include 파일 포함.
<> 컴파일러 설정 디렉터리에서 파일 찾음.
“” 소스파일이 저장된 디렉터리에서 먼저 찾음. 사용자 정의 헤더.
프로젝트 디렉터리가 이닌 곳에서 찾으려면 경로면 포함. 이스케이프 필요 없음.(전처리기가 하는 일이라서 컴파일러처럼 할 필요 없음. 전처리기가 배치 파일이나 배시 스크립트가 간단한 명령을 처리하는 방식과 유사한 느낌이 있다.)
students.h 헤더 파일에서 구조체 정의하고 main.c에서 #include “students.h”하면 헤더 파일 포함됨.
메인 함수 중간에서도 인클루드는 가능하지만….
특별한 이유 없으면 하지 말 것
전처리 후에도 여전히 텍스트 파일임.
매크로명 만들기 #define
#define 매크로명 치환값
매크로 함수
#define 매크로함수명(인수) 치환될 부분
#include <stdio.h>
#define PI 3.14159 // 상수를 매크로명으로 정의
#define LIMIT 100.0 // 상수를 매크로명으로 정의
#define MSG "passed!" // 문자열을 매크로명으로 정의
#define ERR_PRN printf("허용 범위를 벗어났습니다!\\n") // 출력문을 매크로명으로 정의
int main(void)
{
double radius, area; // 반지름과 면적 변수
printf("반지름을 입력하세요(10 이하) : ");
scanf("%lf", &radius); // 반지름 입력
area = PI * radius * radius; // 면적 계산
if (area > LIMIT) ERR_PRN; // 면적이 100을 초과하면 오류 메시지 출력
else printf("원의 면적 : %.2lf(%s)\\n", area, MSG); // 면적과 메시지 출력
return 0;
}
상수 대신에 쓰이는 매크로명은 매크로 상수
치환될 부분이 길어 여러 줄에 걸쳐 쓰려면 \로 연결
#define INTRO "Perfect C Language \\
& Basic Data structure"
#define을 사용한 매크로 함수
#include <stdio.h>
#define SUM(a, b) ((a) + (b)) // 두 값을 더하는 매크로 함수
#define MUL(a, b) ((a) * (b)) // 두 값을 곱하는 매크로 함수
int main(void)
{
int a = 10, b = 20;
int x = 30, y = 40;
int res;
printf("a + b = %d\\n", SUM(a, b)); // a와 b의 합
printf("x + y = %d\\n", SUM(x, y)); // x와 y의 합
res = 30 / MUL(2, 5); // 30을 2와 5의 곱으로 나눔
printf("res : %d\\n", res);
return 0;
}
치환된 후의 상호작용의 불안정성을 방지하기 위해 수식을 괄호로 감싸 둔다.(연산자 우선순위에 의한 오류 방지)
이미 정의된 매크로
__FILE__ //전체 디렉터리 경로를 포함한 파일명
__FUNCTION__ //매크로명이 사용된 함수이름
__LINE__ // 매크로명이 사용된 해 번호
__DATE__ // 컴파일을 시작한 날짜
__TIME__ //컴파일을 시작한 시간
#include <stdio.h>
void func(void);
int main(void)
{
printf("컴파일 날짜와 시간 : %s, %s\\n\\n", __DATE__, __TIME__);
printf("파일명 : %s\\n", __FILE__);
printf("함수명 : %s\\n", __FUNCTION__);
printf("행번호 : %d\\n", __LINE__);
#line 100 "macro.c" // 행 번호를 100부터 시작, 파일명은 macro.c로 표시
func(); // 여기부터 행 번호는 100으로 시작
return 0;
}
void func(void)
{
printf("\\n");
printf("파일명 : %s\\n", __FILE__);
printf("함수명 : %s\\n", __FUNCTION__);
printf("행번호 : %d\\n", __LINE__);
}
__FILE__
과 __LINE__
은 #line
지시자로 정의를 바꿀 수 있음.
프로그램 디버깅에 활용 가능
매크로 연산자 #과
#
은 매크로 함수의 인수를 문자열로 치환
##
은 두 인수를 붙임. cocatenate
#include <stdio.h>
#define PRINT_EXPR(x) printf(#x " = %d\\n", x)
#define NAME_CAT(x, y) (x ## y)
int main(void)
{
int a1, a2;
NAME_CAT(a, 1) = 10; // (a1) = 10;
NAME_CAT(a, 2) = 20; // (a2) = 20;
PRINT_EXPR(a1 + a2); // printf("a1 + a2" " = %d\\n", a1 + a2);
PRINT_EXPR(a2 - a1); // printf("a2 - a1" " = %d\\n", a2 - a1);
return 0;
}
조건부 컴파일 지시자
#include <stdio.h>
#define VER 7 // 치환될 부분이 있는 매크로명 정의
#define BIT16 // 치환될 부분이 없는 매크로명 정의
int main(void)
{
int max;
#if VER >= 6 // 매크로명 VER이 6 이상이면
printf("버전 %d입니다.\\n", VER); // 이 문장 컴파일
#endif // #if의 끝
#ifdef BIT16 // 매크로명 BIT16이 정의되어 있으면
max = 32767; // 이 문장 컴파일
#else // BIT16이 정의되어 있지 않으면
max = 2147483647; // 이 문장 컴파일
#endif // #ifdef의 끝
printf("int형 변수의 최댓값 : %d\\n", max); // max 출력
return 0;
}
#ifdef
는 #if
+ defined
#ifndef
는 #if
+ !defined
다른 연산자와 함께 쓰려면 분리된 형태 사용.
#pragma 지시자
컴파일 방법을 세부지시. 지시명으로 어떤 기능을 제어할 것인지 알려줌.
pack은 구조체의 패딩바이트 크기 결정
warning은 경고 메시지 관리
#include <stdio.h>
#pragma pack(push, 1) // 바이트 얼라인먼트를 1로 바꿈
typedef struct
{
char ch;
int in;
} Sample1;
#pragma pack(pop) // 바꾸기 전의 바이트 얼라인먼트 적용
typedef struct
{
char ch;
int in;
} Sample2;
int main(void)
{
printf("Sample1 구조체의 크기 : %d바이트\\n", sizeof(Sample1));
printf("Sample2 구조체의 크기 : %d바이트\\n", sizeof(Sample2));
return 0;
}
바이크 얼라인먼트 크기를 조절
분할 컴파일
방법
- 두 수의 평균을 구하는 프로그램
main.c
#include <stdio.h>
void input_data(int*, int*); // 두 정수를 입력하는 함수 선언
double average(int, int); // 평균을 구하는 함수 선언
int main(void)
{
int a, b;
double avg;
input_data(&a, &b); // 두 정수 입력
avg = average(a, b); // 평균 계산
printf("%d와 %d의 평균 : %.1lf\\n", a, b, avg); // 입력값과 평균 출력
return 0;
}
sub.c
#define _CRT_SECURE_NO_WARNINGS // scanf 함수 사용을 위한 매크로
#include <stdio.h> // printf, scanf 함수 사용을 위해 필요
void input_data(int* pa, int* pb) // 두 정수 입력 함수
{
printf("두 정수 입력 : ");
scanf("%d%d", pa, pb);
}
double average(int a, int b) // 평균을 구하는 함수
{
int tot;
double avg;
tot = a + b;
avg = tot / 2.0;
return avg;
}
- 프로젝트에 main.c추가하고 컴파일-디버그폴더에 목적파일 생성
- 프로젝트에 sub.c추가하고 컴파일-역시 목적파일 생성.
- 링크([빌드]-[솔루션 빌드])-실행파일 생김.
주의사항
- 각 파일을 독립적으로 컴파일 할 수 있도록 필요한 선언을 포함해야 함.
분할 컴파일에서 extern과 static의 용도
extern
다른 파일에 선언된 전역 변수 사용.
static
다른 파일이 사용하지 못하게 함.
extern
- 용도:
extern
키워드는 다른 파일에서 정의된 전역 변수나 함수를 참조할 때 사용합니다. 즉, 변수나 함수가 외부에 선언되어 있음을 명시하고, 링크 시점에 해당 심볼의 정의를 찾아 연결합니다. - 사용 예:
// file1.c에서 정의 int global_var = 10; // file2.c에서 사용 extern int global_var;
- 주의사항:
extern
변수를 사용할 때는 실제로 해당 변수가 다른 파일에 정의되어 있어야 합니다. 정의되지 않은 상태에서 사용하면 링커 오류가 발생할 수 있습니다.- 초기화:
extern
으로 선언된 변수는 초기화하면 안 됩니다. 초기화는 정의와 함께 이루어져야 하며,extern
은 정의가 아닌 선언에 사용됩니다. - 헤더 파일:
extern
변수는 헤더 파일에 선언하고, 한 소스 파일에서만 정의하는 것이 일반적입니다. 이렇게 하면 여러 소스 파일에서 같은 전역 변수를 쉽게 공유할 수 있습니다.
static
- 용도:
static
키워드는 변수나 함수의 범위를 해당 파일 내로 제한합니다. 즉, static으로 선언된 변수나 함수는 다른 파일에서 접근할 수 없게 됩니다. - 사용 예:
// file1.c 내에서만 접근 가능 static int file1_local_var = 20; static void file1_function() { // 내용 }
- 범위 제한:
static
으로 선언된 변수나 함수는 다른 파일에서 접근할 수 없습니다. 이는 의도적으로 정보 은닉을 할 때 유용합니다. - 초기화: 전역
static
변수는 자동으로 0으로 초기화됩니다. 로컬static
변수도 선언 시 명시적으로 초기화하지 않으면 0으로 초기화됩니다. - 메모리:
static
변수는 프로그램의 생명 주기 동안 계속 존재하므로, 프로그램이 종료될 때까지 메모리를 차지하게 됩니다.주의사항:
- 범위 제한:
헤더 파일의 필요성과 중복 문제 해결 방법
헤더 파일이 여러 번 포함될 때 중복 정의 문제가 발생할 수 있습니다. 예를 들어, 두 개의 서로 다른 헤더 파일이 서로를 포함하고 있다면, 컴파일러는 같은 선언을 여러 번 처리하게 되어 문제를 일으킬 수 있습니다. 이러한 문제를 해결하기 위한 주요 방법은 다음과 같습니다.
- 함수 선언 공유: 헤더 파일을 통해 여러 소스 파일에서 사용할 함수의 선언을 공유할 수 있습니다. 이를 통해 함수가 정의된 소스 파일과 다른 소스 파일에서도 해당 함수를 사용할 수 있게 됩니다.
- 타입 정의 공유: 사용자 정의 타입(예: 구조체, 열거형, 타입 별칭)을 헤더 파일에 선언하여 여러 소스 파일에서 이를 공유할 수 있습니다.
- 매크로 정의: 매크로 상수나 매크로 함수를 헤더 파일에 정의함으로써 다양한 소스 파일에서 이를 재사용할 수 있습니다.
- 모듈화 및 재사용: 헤더 파일은 코드의 모듈화를 촉진하고, 쉽게 재사용할 수 있게 해줍니다. 이는 프로젝트의 유지보수성을 높이고, 오류 가능성을 줄입니다.
- 중복 포함 문제 및 해결 방법
- Include Guards: 인클루드 가드는 헤더 파일이 한 번만 포함되도록 보장합니다. 이는 전처리기 지시어를 사용하여 구현됩니다. 가장 일반적인 형태는
#ifndef
,#define
,#endif
를 사용하는 것입니다.- 예시:
// file.h #ifndef FILE_H #define FILE_H void function(); // 다른 선언들 #endif
- Pragma Once: 일부 컴파일러에서는
#pragma once
지시어를 제공하여 동일한 헤더 파일이 여러 번 포함되는 것을 방지합니다. 이 지시어는 파일 전체에 대한 유일한 포함을 보장합니다.- 예시:
// file.h #pragma once void function(); // 다른 선언들
#pragma once
는 간단하고 효율적이지만, 모든 컴파일러에서 지원되는 것은 아니므로, 포터블 코드를 작성하려면 인클루드 가드를 사용하는 것이 더 일반적입니다. 각 방법은 코드의 중복 포함을 방지하고, 프로젝트의 관리를 더욱 효율적으로 만들어 줍니다. - Include Guards: 인클루드 가드는 헤더 파일이 한 번만 포함되도록 보장합니다. 이는 전처리기 지시어를 사용하여 구현됩니다. 가장 일반적인 형태는