티스토리 뷰

 

본 강좌는 아래 동영상 강좌와 같이 진행됩니다. 되도록이면 동영상과 같이 보시는 것을 추천합니다.

 

 유튜브 채널 가기

 

 강좌 20편 동영상 보기

 


 

 이번시간에는 프로그램 소스를 여러 파일로 분할하는 방법에 대해 알아보도록 하겠습니다.

 

 

 

 1. 파일 경로의 표현 방법

 

 C언어에서 특정한 곳에 존재하는 파일의 위치를 나타내는 방법은 크게 두가지가 있습니다.

 

 ◆ 절대 경로

 절대 경로는 가장 상위의 경로부터 차례대로 파일의 위치까지를 써주는 방식입니다. 예를 들어봅시다.

 

C:\Windows\System32\a.dll

D:\Temp\image.jpg

 

 위와 같이 우리가 흔히 사용하는 경로의 표현방법과 같습니다. 드라이브명부터 각각의 폴더명을 나열하고 마지막에 파일명이 위치하게 되며, 드라이브명, 폴더명, 파일명을 구분하기 위해 역슬레시 '\'를 사용합니다.

 

 ◆ 상대 경로

 상대 경로는 현재 파일을 기준으로 다른 파일의 위치까지를 상대적으로 써주는 방식입니다. 예를 들어

 

 다음과 같은 폴더에 'a.c'라는 파일이 있다고 합시다. 이 파일을 기준으로 다음과 같이

 

 'c.c' 파일을 사용하려 한다고 했을때의 상대 경로는

 

CCC\c.c

 

 와 같습니다. 반대로 'c.c' 파일을 기준으로 'a.c' 파일의 상대경로는

 

..\a.c

 

 와 같습니다.

 

 상대경로를 사용하는데에는 몇가지 규칙이 있습니다.

 

1. 기준이 되는 파일의 위치를 기준으로 해서 순서대로 폴더명과 파일명을 써준다.

2. 절대경로와 마찬가지로 폴더, 파일명 사이는 '\'로 구분한다.

3. 폴더를 한단계 밖으로 나가야 할 경우에는 '..'을 사용한다.

 

  상대경로의 장점은 폴더 통째로 다른 곳에 복사해놓아도 해당 폴더안의 파일들끼리는 상대경로가 변하지 않는다는 점입니다. 만약에 위의 폴더들을 아래처럼 다른 폴더로 복사한다고 가정해 봅시다.

 

 'temp' 폴더 자체를 'TTT' 폴더에 복사하였습니다. 복사하기 전과 후의 절대 경로를 비교해봅시다.

 

절대 경로

복사하기 전

 

C:\temp\AAA\a.c

C:\temp\AAA\CCC\c.c

 

복사한 후

 

C:\TTT\temp\AAA\a.c

C:\TTT\temp\AAA\CCC\c.c

 

 위와 같이 경로 자체가 바뀌는걸 알 수 있습니다. 반면에 상대경로는

 

상대 경로

복사하기 전

 

CCC\c.c

..\a.c

 

복사한 후

 

CCC\c.c

..\a.c

 

 이와 같이 변하지 않습니다. 그래서 소스코드 내에 특정 파일의 위치를 표현하고자 할때는 보통 '절대 경로' 보다는 '상대 경로'를 많이 사용합니다.

 

 

 

 2. 소스 분할

 

 이제 소스코드를 여러 파일로 분할해 봅시다. 우선 다음과 같은 소스가 있다고 합시다.

 

#include <stdio.h>

 

int sum(int a, int b);

int a = 5, b = 8;

 

void main()

{

printf("%d + %d = %d\n", a, b, sum(a, b));

}

 

int sum(int a, int b)

{

return a + b;

}

 

 int형 변수 'a'와 'b'를 더하는 함수를 만들었고, printf()문을 사용해 출력하는 간단한 코드입니다. 이것을 다른 파일로 분할해보겠습니다.

 

 '솔루션 탐색기'에서 '헤더 파일' 부분에 마우스 커서를 올려놓고 오른쪽 버튼을 누릅니다.

 

 메뉴가 열리면 '추가' → '새 항목' 을 고릅니다.

 

 '새 항목 추가' 창이 열립니다. 위에서는 '헤더 파일' 항목을 고르고, 아래 '이름' 부분에 적당한 이름을 써 넣은 다음, 오른쪽 아래 '추가' 버튼을 누릅니다.

 

 그러면 다음과 같이 '솔루션 탐색기'의 '헤더 파일' 부분에 파일이 추가가 되고, 그 파일이 편집 가능하게 열린 것을 볼 수 있습니다. 여기서 기본적으로 추가된 '#pragma once' 부분은 지워줍니다.

 

 이렇게 만든 파일을 '헤더 파일' 이라 하고, 이것을 이용해 소스코드를 분할합니다. '헤더 파일'이란 일종의 관문 또는 메뉴판 같은 역할을 하며, 여기에는 각종 선언들이 위치하게 됩니다. 반대로 확장자 'c'인 파일들은 '소스 파일' 이라고 하며, 실제로 프로그램의 기능을 구현하는 소스코드를 넣어주게 됩니다.

 

 이제 추가한 헤더파일을 편집해 봅시다.

 

int sum(int a, int b);

 

int a = 5, b = 8;

 

int sum(int a, int b)

{

return a + b;

}

 

 소스파일에서 함수의 선언, 정의 부분과 변수 선언 부분을 가져왔습니다. 소스파일은

 

#include <stdio.h>

 

void main()

{

printf("%d + %d = %d", a, b, sum(a, b));

}

 

 이렇게 해당 부분을 '헤더 파일'로 옮겼기 때문에 나머지 부분만 있게 됩니다. 이제 '헤더 파일'을 '소스 파일'에 포함시켜 줍시다.

 

#include "calc.h"

 

 이 부분을 소스파일의 맨 위에 넣어주고 실행을 해봅시다.

 

 에러 없이 결과가 정상적으로 나오는걸 볼 수 있씁니다.

 

 여기서 '#include'문이 나오는데 이것은 뒤에 오는 '헤더 파일'을 여기에 포함 시키라는 의미라는것은 예전에 설명한 적이 있습니다. 그런데 지금까지와는 달리 <> 대신 "" 로 파일명을 감싸 주었습니다. 이유는 아래와 같습니다.

 

<> : 기본 라이브러리 폴더 안에 있을때

 "" : 그 밖의 다른 위치에 있을때

 

 여기서 '기본 라이브러리 폴더'라는 것은 비주얼 스튜디오 설치 폴더 안에 기본으로 설치된 'stdio.h' 같은 파일들이 들어있는 폴더를 말합니다. 이곳이 아닌 우리가 만든 헤더 파일을 포함하기 위해서는 "" 를 써주게 됩니다.

 

 그런데, C언어의 헤더 파일에 함수를 구현한 '정의' 부분을 넣는 것 보다는 분리하는게 깔끔하고 알아보기 편합니다. 이 부분을 별도의 소스 파일에 넣어 보도록 하겠습니다. '솔루션 탐색기' 에서 '소스 파일' 부분에 마우스 커서를 올려 놓고 오른쪽 버튼을 누릅니다.

 

 나오는 메뉴에서 '추가' → '새 항목' 을 고릅니다.

 

 '새 항목 추가' 창이 열립니다. 중간의 'C++ 파일' 을 고르고 아래 '이름' 부분에는 아까 만들었던 헤더 파일의 이름과 같은 이름으로 확장자만 'c'로 하여 써주고, 오른쪽 아래 '추가' 버튼을 누릅니다.

 

 그러면 다음과 같이 '소스 파일' 부분에 추가된 파일이 보이고, 편집할 수 있는 상태가 됩니다. 여기에 다음과 같은 내용을 넣어 봅시다.

 

#include "calc.h"

 

int sum(int a, int b)

{

return a + b;

}

 

 함수의 정의 부분을 넣어주었습니다. 그렇기 때문에 헤더 파일에는

 

int sum(int a, int b);

 

int a = 5, b = 8;

 

 이렇게 남게 됩니다. 한번 실행해 봅시다.

 

 뭔가 에러가 발생하는데 그 이유는 전역 변수는 전체 프로그램 내에서 중복된 이름을 가질 수 없는데, 두개의 소스 파일에서 'calc.h' 파일을 포함하였기 때문에, 결과적으로 변수 'a', 'b'는 두번 선언된 것이기 때문입니다. 이것을 해결하려면 'extern' 이란 것을 사용합니다.

 

 

 

 

 3. 외부 변수

 

 'extern'이란 특정한 전역 변수를 다른 파일에서도 사용할 수 있게 합니다. 예를 들어

 

a.c 안의 내용

 

...

int a = 10;

...

 

 이와 같이 전역 변수가 선언이 되어 있는데 'b.c'에서 사용하고자 한다면

 

b.c 안의 내용

 

extern int a;

 

 이처럼 같은 이름의 전역 변수를 선언하고, 그 앞에 'extern'을 붙여 주면 같은 변수를 공유할 수 있게 됩니다. 초기화는 이미 'a.c'에서 해주었으므로 여기서는 해주지 않습니다.

 

 이제 위에서 소스 분할 하던 내용을 마무리해봅시다. 'calc.c'의 내용을 다음과 같이 고쳐줍니다.

 

#include "calc.h"

 

int a = 5, b = 8;

 

int sum(int a, int b)

{

return a + b;

}

 

 헤더 파일  'calc.h' 는 다음과 같이 고쳐줍니다.

 

int sum(int a, int b);

 

extern int a, b;

 

 이렇게 헤더파일에 'extern'으로 변수를 선언하게 되면, 이 해더 파일을 다른 파일에 '#include'로 포함하는 것만으로 a, b 두 변수를 사용할 수 있게 됩니다.

 

 'extern'을 사용하게 되면 변수 선언이 여기저기 흩어지게 되어, 변수가 많아질수록 프로그램이 복잡해지게 되기 때문에 꼭 필요한 경우가 아니라면 되도록 사용하지 않는 것이 좋습니다.

 

 

 

 4. 성적관리 프로그램 분할하기

 

 이제 성적관리 프로그램도 여러 파일로 분할해 봅시다. 우선 'my.h'와 'my.c' 파일을 만듭니다.

 

 다음과 같이 추기된 것을 확인한 후, 분할하기 편하도록 'main.c'의 메모리 해제 부분을 함수로 만들어 줍니다.

 

int FreeMemory(int index);

 

// 메모리 해제
int FreeMemory(int index)
{

while (index)
{

index--;
free(student[index].comment);
student[index].comment = NULL;

}

 

return 0;

}

 

  이제 main() 함수의 다음 부분을 고쳐줍니다.

 

...

 

default:   // 예외 처리

{
      printf("\n잘못 입력 하셨습니다.\n");

getchar();

}

}

} while (menuChoice != 'Q' && menuChoice != 'q');

 

// 메모리 해제
FreeMemory(index);

}

 

  다음으로 'main.c'의 해당 부분을 잘라내어 'my.h'에 붙여 넣어줍니다.

 

// 구조체
typedef struct _student
{

char name[20];     // 이름
int scoreKOR;     // 국어점수
int scoreMAT;     // 수학점수
int scoreENG;     // 영어점수
int scoreSCI;     // 과학점수
char *comment;    // 평가

} STUDENT;

 

// 입력
int inputGrade(int index);

 

// 계산
int calcGrade(int index, int *scoreTotal, float *scoreAverage, char *grade);

 

// 출력
int outputGrade(int index);

 

// 메모리 해제
int FreeMemory(int index);

 

 이번에는 'main.c'의 해당 부분을 잘라내 'my.c'에 붙여 넣어 주고, 맨 위에 '#include' 문을 추가합니다.

 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "my.h"

 

STUDENT student[20];

 

// 입력
int inputGrade(int index)
{

char temp[1024] = "\0";   // 임시 저장
int len = 0;                   // 문자열 길이

 

do

{

printf("이름을 입력하세요 : ");
scanf_s("%s", temp, sizeof(temp));


// 문자열 길이 구하기
len = strlen(temp) + 1;

 

// 문자열 길이가 길면
if (len > sizeof(student[index].name))

printf("이름이 너무 깁니다.\n");

else     // 길이가 짧으면

strcpy_s(student[index].name, sizeof(student[index].name), temp);

} while (len > sizeof(student[index].name));

 

printf("국어점수를 입력하세요 : ");
scanf_s("%d", &student[index].scoreKOR);
printf("수학점수를 입력하세요 : ");
scanf_s("%d", &student[index].scoreMAT);
printf("영어점수를 입력하세요 : ");
scanf_s("%d", &student[index].scoreENG);
printf("과학점수를 입력하세요 : ");
scanf_s("%d", &student[index].scoreSCI);

 

getchar();


printf("평가를 입력하세요 : ");
gets_s(temp, sizeof(temp));
len = strlen(temp) + 1;
student[index].comment = (char*)malloc(len);
strcpy_s(student[index].comment, len, temp);

 

return 0;

}

 

/ 계산
int calcGrade(int index, int *scoreTotal, float *scoreAverage, char *grade)
{

// 총점 구하기
*scoreTotal = student[index].scoreKOR + student[index].scoreMAT + student[index].scoreENG +     student[index].scoreSCI;


// 평균 구하기
*scoreAverage = *scoreTotal / 4.0F;

 

// 등급
if (*scoreAverage >= 90)

*grade = 'A';

else if (*scoreAverage >= 80)

*grade = 'B';

else if (*scoreAverage >= 70)

*grade = 'C';

else if (*scoreAverage >= 60)

*grade = 'D';

else

*grade = 'F';

 

return 0;

}

 

// 출력
int outputGrade(int index)
{

char grade = '\0';  // 등급
int scoreTotal = 0; // 총점
float scoreAverage = 0.0F;  // 평균

calcGrade(index, &scoreTotal, &scoreAverage, &grade);

 

 // 화면 출력
 printf("%s\t\t", student[index].name);
 printf("%c\t", grade);
 printf("%d\t", student[index].scoreKOR);
 printf("%d\t", student[index].scoreMAT);
 printf("%d\t", student[index].scoreENG);
 printf("%d\t", student[index].scoreSCI);

printf("%d\t", scoreTotal);
printf("%.1f\t", scoreAverage);
printf("%s\n", student[index].comment);

 

return 0;

}

 

// 메모리 해제
int FreeMemory(int index)
{

while (index)
{

index--;
free(student[index].comment);
student[index].comment = NULL;

} // end while

 

return 0;

}

 

 끝으로 'main.c' 윗부분을 다음과 같이 고쳐줍니다.

 

#include <stdio.h>

#include "my.h"


void main()
{

...

 

 이것으로 성적관리 프로그램의 소스코드를 분할해보았습니다. 앞으로 main() 함수에는 메뉴 선택과 같은 기본적인 것들 위주만 구현하도록 하고, 세부적인 기능들은 위에서 만든 'my.h'와 'my.c'에 함수 형태로 구현하면 될 것 같습니다.

 

 다음 시간에는 위에서 잠깐 살펴본 '#include'문과 같은 '전처리문'에 대해 알아보도록 하겠습니다.

 

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31