티스토리 뷰

주의 사항!

  • 이 일지는 작성하고 있는 현시점, 공부와 병행하면서 작성되고 있습니다.
  • 공부 중에 떠오른 생각이나 그때그때의 개념정리 같은 내용이 포함됩니다.
  • 따라서 이 일지의 내용은 제가 공부하고 이해한 대로 작성되기 때문에 실제 사실과는 다를 수 있습니다.

 

하나의 프로그램을 여러 개의 소스 파일로 나누어 각각 독립적으로 컴파일하면

디버깅하기 쉽고 유지 보수와 코드 재활용에 유리합니다.

 

분할 컴파일은 하나의 프로그램을 여러 개의 파일로 나누어 작성하면 됩니다.

간단한 예제를 통해 분할 컴파일 방법을 살펴보겠습니다.

#include <stdio.h>

void inputData(int*, int*);
double average(int, int);

int main(void)
{
	int a, b;
	double avg;

	inputData(&a, &b);
	avg = average(a, b);

	printf("%d와 %d의 평균 : %.1lf\n", a, b, avg);

	return 0;
}

먼저 위 소스코드를 main.c로 저장합니다.

#include <stdio.h>

void inputData(int* pa, int* pb)
{
	printf("두 정수 입력 : ");
	scanf("%d%d", pa, pb);
}

double average(int a, int b)
{
	double avg;
	avg = (a + b) / 2.0;

	return avg;
}

그리고 위 코드는 sub.c로 저장합니다.

 

main.c에서는 두 함수 inputData와 average가 사용되었지만 두 함수가 정의되어 있진 않습니다.

이 두 함수는 sub.c에서 선언되었습니다.

이 둘을 컴파일하면 main.obj와 sub.obj가 프로젝트 디렉터리의 Debug디렉터리에 생성됩니다.

두 개체파일이 모두 생성되면 솔루션 빌드를 선택하여 링크를 수행합니다.

링크가 성공저긍로 끝나면 Debug 디렉터리에 프로젝트와 이름이 같은 실행파일이 생성됩니다.

 

따라서 분할 컴파일을 하면 각 파일은 개별적으로 컴파일된 후 링크 단계에서 합쳐져 하나의 실행파일이 됩니다.

 

분할 컴파일을 위해 파일을 나눠줄 때는 주의할 것이 있습니다.

각 파일을 독립적으로 컴파일할 수 있도록 필요한 선언을 포함해야 합니다.

 

main.c에는 inputData 함수와 average 함수가 정의되진 않지만 사용하고 있기 때문에 함수의 선언을 해주었습니다.

그리고 #include <stdio.h>는 main.c 와 sub.c에 중복으로 들어가지만

각각의 소스파일을 컴파일하기 위해서 둘 모두에 넣어주었습니다.

또한 모든 파일을 성공적으로 컴파일한 뒤에 림크를 수행해야 합니다.

 

프로젝트에 항상 새로운 소스파일만 추가할 수 있는 것은 아닙니다.

이미 만들어진 소스 파일이나 컴파일된 개체 파일도 프로젝트에 포함할 수 있습니다.

이때 소스파일은 프로젝트 디렉터리에 저장하고 개체 파일은 프로젝트 디렉터리의 Debug 디렉터리에 저장합니다.

 

분할 컴파일을 하면 프로그램을 나눠 작성하고 파일별로 에러를 수정할 수 있으므로

규모가 큰 프로그램도 쉽게 만들 수 있습니다.

또한 기능이 검증된 소스파일은 다른 프로그램에서도 사용할 수 있으므로 코드의 재활용에 도움이 됩니다.


프로그램을 여러 개의 파일로 나누면 각 파일들이 전역변수를 공유하기가 쉽지 않습니다.

컴파일러는 소스파일 단위로 컴파일하므로 다른 파일에 선언된 전역변수를 알 수 없기 때문입니다.

 

다른 파일에 선언된 전역변수를 사용할 때는 extern 선언을 합니다.

반면에 다른 파일에서 전역 변수를 공유하지 못하게 할 때는 static을 사용합니다.

예제를 통해 사용법을 살펴보겠습니다.

#include <stdio.h>

int inputData(void);
double average(void);
void printData(double);

int count = 0;
static int tot = 0;

int main(void)
{
	double avg;

	tot = inputData();
	avg = average();
	printData(avg);

	return 0;
}

void printData(double avg)
{
	printf(" 입력한 양수의 개수 : %d\n", count);
	printf(" 전체 합과 평균 : %d, %.1lf\n", tot, avg);
}

먼저 위 소스파일을 main.c로 저장합니다.

#include <stdio.h>

extern int count;
int tot = 0;

int inputData(void)
{
	int pos;

	while (1)
	{
		printf("양수 입력 : ");
		scanf("%d", &pos);
		if (pos <= 0) break;
		count++;
		tot += pos;
	}

	return tot;
}

이후 위 소스파일을 input.c로 저장합니다.

extern int count;
extern int tot;

double average(void)
{
	return tot / (double)count;
}

위 소스파일도 average.c로 저장합니다.

 

세 소스파일을 컴파일하고 링크를 수행 후 실행하면 다음과 같은 결과를 얻을 수 있습니다.

/*
실행결과

양수 입력 : 1
양수 입력 : 2
양수 입력 : 3
양수 입력 : 45
양수 입력 : 0
 입력한 양수의 개수 : 4
 전체 합과 평균 : 51, 12.8

*/

어느 한 소스파일의 변수를 다른 소스파일에서도 사용할 수 있게 하기 위해서는

해당 변수를 전역 변수로 선언해야 합니다.

 

먼저, main.c 파일에서 전역변수 count와 tot를 선언했습니다.

그런데 tot는 앞에 static을 붙여 선언했습니다.

이렇게 static을 붙여서 전역 변수를 선언하면 다른 소스파일에서는 이 변수를 공유할 수 없게 됩니다.

 

input.c 소스파일을 보면 count 변수는 extern을 붙여 앞서 선언했던 main.c의 count를 공유합니다.

하지만 변수 tot는 공유할 tot변수가 없기 때문에 tot를 전역 변수로 다시 선언해주었습니다.

 

average.c 소스파일에서는 count와 tot 모두 extern을 붙였습니다.

이때 count는 main.c 에서 선언된 것을 공유하고, tot는 input.c에서 선언된 것을 공유합니다.

main.c에서 static으로 선언된 tot는 공유하지 못합니다.

 

만약 다른 소스파일의 전역 변수를 공유할건데 앞에 extern을 붙이지 않으면

해당 소스파일을 컴파일 하는데에는 문제가 없으나 링크하는 과정에서 문제가 발생합니다.

extern을 붙이지 않으면 전역 변수를 새로 정의하는 것으로 컴파일러는 판단합니다.

하지만 링크하는 단계에서 다른 소스파일에 똑같은 이름의 전역 변수가 이미 있다는 걸 알게 되므로 에러를 유발합니다.

 

반면 static은 해당 소스파일에서만 해당 전역 변수를 사용할 수 있게 합니다.

따라서 다른 소스파일에서 같은 이름의 전역 변수를 새로 선언하고 사용할 수 있습니다.

 

static은 함수 앞에도 사용할 수 있습니다.

함수 앞에 사용하게 되면 해당 함수는 해당 소스파일 내에서만 사용할 수 있게 됩니다.

함수의 경우 앞에 extern을 붙이지 않아도 링크 단계에서 같은 이름의 함수가 있을 때 extern으로 간주합니다.


하나의 함수를 여러 파일에서 사용하는 경우 각 파일에는 모두 같은 함수 선언이 필요합니다.

또는 하나의 전역 변수를 여러 파일에서 공유하는 경우 각 파일에는 모두 같은 extern 선언이 필요합니다.

뿐만 아니라 구조체 선언이 여러 파일에 동시에 필요할 수도 있습니다.

따라서 함수의 선언이나 extern 선언, 구조체 선언 등을 헤더파일로 만들면

필요할 때 인클루드하여 쉽게 공유할 수 있습니다.

 

또한 헤더 파일의 내용을 수정하더라도 전처리 과정에서 인클루드 하는 모든 파일에

수정된 내용을 빠르고 정확하게 반영할 수 있습니다.

물론 다른 프로그램에 재활용하는 것도 가능합니다.

다만 헤더 파일을 재활용하는 경우 구조체 등이 중복 선언될 수 있으므로 이를 해결해야 합니다.

 

예제를 통해 살펴보겠습니다.

#ifndef _POINT_H_
#define _POINT_H_

typedef struct
{
	int x;
	int y;
} Point;

#endif

위 헤더 파일을 point.h로 저장합니다.

#include "point.h"

typedef struct
{
	Point first;
	Point second;
} Line;

위 헤더 파일을 line.h로 저장합니다.

#include <stdio.h>
#include "point.h"
#include "line.h"

int main(void)
{
	Line a = { {1,2}, {5,6} };
	Point b;
	b.x = (a.first.x + a.second.x) / 2;
	b.y = (a.first.y + a.second.y) / 2;
	printf("선의 가운데 점의 좌표 : (%d, %d)\n", b.x, b.y);

	return 0;
}

위 소스 파일을 main.c로 저장합니다.

 

line.h 와 main.c를 함께 살펴 보면 각각 point.h를 인클루드하고 있습니다.

main.c에서 point.h를 한 번 인클루드하고, line.h를 인클루드할 때 결국 point.h가 또 다시 인클루드됩니다.

point.h에는 Point 구조체가 선언되므로 결국 main.c를 컴파일하면 Point 구조체가 두 번 선언되어 중복됩니다.

이는 오류를 발생시킵니다.

 

분할 컴파일에서 extern과 함수는 중복으로 선언될 수 있습니다.

하지만 구조체의 경우엔 중복으로 선언될 수 없습니다.

따라서 point.h에서 조건부 전처리 지시자를 달아 Point 구조체가 중복으로 선언되지 않도록 하였습니다.

물론 main.c에서 line.h만 인클루드하여 구조체 중복 선언을 방지할 수도 있습니다.

 

모든 헤더 파일을 만들 때는 매크로명을 처음에 정의하여 같은 헤더 파일이 두 번 이상 포함되지 않도록 해야 합니다.

 

다음 시간엔 전처리와 분할 컴파일 실전문제를 풀어보겠습니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/01   »
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
글 보관함