2024. 3. 11. 23:24ㆍProgramming Language/C++
아래 링크 클릭 시 해당 본문으로 이동
이전에는 하나의 cpp 파일 안에서 함수를 만들고 `main()`에서 함수를 호출하는 방식을 사용했었다.
int gInt = 0;
// Test() 정의
void Test()
{
++gInt;
}
int main()
{
Test(); // Test() 호출
Test();
Test();
int a = gInt;
return 0;
}
그렇다면 분할 구현은 무엇이고 왜 사용하며, 사용했을 때 문제점은 무엇일까?
분할 구현
• 코드를 편하게 재사용하기 위해 헤더와 파일을 분할해서 선언과 정의를 분리시켜서 작업하는 것
• 헤더 파일 (.h) : 함수를 선언하는 곳
• cpp 파일 (.cpp) : 헤더 파일에 선언된 함수를 정의하는 곳
분할 구현을 사용하는 이유
① 필요할 때마다 함수가 선언된 헤더 파일을 `#include`로 불러올 수 있다.
필요할 때마다 해당 헤더 파일을 `#include`로 불러오면 여러 파일과 공유가 가능하다.
같은 함수를 여러 cpp 파일에 써야 할 때 헤더 파일에 함수를 선언하면 함수를 일일이 복사·붙여 넣기 하지 않아도 된다.
ex) 몬스터에 대한 함수 선언 코드를 담은 `monster.h`를 만들고 `#include "monster.h"`로 불러올 수 있다.
② 코드량을 줄일 수 있다.
코드를 작성하다 보면 점점 길어지고, 하나의 파일에 코드를 전부 작성하면 코드량이 엄청나게 늘어난다.
이때 분할 구현으로 파일을 분리하면 코드량을 줄일 수 있다.
헤더와 파일 분리하기
▽ 헤더 파일과 cpp 파일 생성
헤더 파일에는 해당 함수가 어떻게 동작하는지 실제 동작 코드는 구현하지 않고(함수를 정의하지 않고) 이런 함수가 있다는 선언만 한다.
func.h
`Add()` 선언
#progma once
int Add(int a, int b); // Add() 선언
반환 타입이 `int`이고, 인자로 int형 2개를 받는 `Add()`를 선언한다.
• 인자 : 함수 호출할 때 실제로 전달되는 값
func.cpp
`Add()` : int형 인자 2개를 서로 더한다.
#include "func.h" // int Add(int a, int b);
int Add(int a, int b)
{
return a + b;
}
`#include "func.h"` : `func.h`라는 파일을 여기로 가져온다.
`#include "func.h"` = `int Add(int a, int b);`
➜ `#include "func.h"`자리에 `int Add(int a, int b);`가 있다고 생각하면 된다. (a.k.a 복사 + 붙여 넣기)
split.cpp
`Add()`를 호출하여 `10`과 `20`을 더한 값을 int형 변수 `data`에 저장하겠다.
정의한 함수를 가져와서 프로그래밍한다.
#include "func.h" // int Add(int a, int b);
int main()
{
int data = Add(10, 20);
return 0;
}
▷ 사칙연산
math.h
사칙연산 함수 선언
#pragma once
int Add(int a, int b); // 더하기
int Mul(int a, int b); // 빼기
int Sub(int a, int b); // 곱하기
int Div(int a, int b); // 나누기
다른 코드에서 사칙 연산이 필요하면 `"math.h"`만 `#include`로 파일을 불러오면 된다.
math.cpp
사칙연산 함수 정의
#include "math.h" // int Add(int a, int b);
// int Mul(int a, int b);
// int Sub(int a, int b);
// int Div(int a, int b);
// 더하기
int Add(int a, int b)
{
return a + b;
}
// 빼기
int Sub(int a, int b)
{
return a - b;
}
// 곱하기
int Mul(int a, int b)
{
return a * b;
}
// 나누기
int Div(int a, int b)
{
return a / b;
}
main.cpp
사칙연산 함수를 호출하여 int형 인자 2개를 각각의 함수에 맞게 연산하고, 연산한 값은 해당하는 변수에 저장한다.
#include "math.h" // int Add(int a, int b);
// int Mul(int a, int b);
// int Sub(int a, int b);
// int Div(int a, int b);
int main()
{
int iAdd = Add(10, 20); // 30
int iSub = Sub(10, 20); // -10
int iMul = Mul(10, 20); // 200
int iDiv = Div(10, 20); // 0
return 0;
}
헤더 파일에 함수를 정의했을 때 발생하는 문제
어차피 헤더 파일을 `#include`로 불러오니까 헤더 파일에 함수 정의를 하면 왜 안 되는 걸까?
헤더 파일에 함수 정의를 하게 되면 어떤 문제점이 있는지 알아보자.
1. 함수 중복 문제
▷ 하나의 파일 안에 동일한 함수명을 2개 만든 코드
int Walk()
{
}
// 오류
int Walk()
{
}
int main()
{
Walk();
return 0;
}
두 번째 `Walk()`에서 오류가 발생한다.
'함수에 이미 본문이 있습니다.'는 똑같은 함수명이 이미 있다는 뜻이다.
함수 호출을 했을 때 어떤 함수를 호출한 것인지 알 수 없기 때문에 하나의 파일에 동일한 함수명을 사용할 수 없다.
헤더 파일에 함수를 정의하고 각각의 cpp 파일에 헤더 파일을 불러오면 발생하는 문제
`func.h`에 `Add()`를 정의하고 각각의 cpp 파일에서 `#include "func.h"`로 헤더 파일을 불러왔다.
func.h
#pragma once
//int Add(int a, int b); // 함수 선언
// 함수 정의
int Add(int a, int b)
{
return a + b;
}
func.cpp | main.cpp | split.cpp |
#include "func.h" | #include "func.h" | #include "func.h" |
=
func.cpp | main.cpp | split.cpp |
int Add(int a, int b) { return a + b; } |
int Add(int a, int b) { return a + b; } |
int Add(int a, int b) { return a + b; } |
파일 단위로 개별적으로 컴파일되는 obj 파일이 있다.
`func.obj` 파일 = `func.cpp` 파일이 컴파일된 obj 파일
`main.obj` 파일 = `main.cpp` 파일이 컴파일된 obj 파일
`split.obj` 파일 = `split.cpp` 파일이 컴파일된 obj 파일
링킹 단계에서 코드를 하나로 합쳐서 실행 파일(.exe)이 만들어진다.
cpp 파일마다 `Add()`가 들어가 있어서 코드가 하나로 합쳐지면 결국 `Add()`는 3개가 되어버려서 함수가 중복되는 문제가 발생한다.
2. 전역 변수 사용 문제
전역 변수는 데이터 영역에 계속 존재하므로 해당 파일 안에서는 전역 변수를 어디에 사용하든지 상관이 없다.
하지만 전역 변수를 선언한 파일과 다른 파일에서는 그 전역 변수가 있는지 알 수가 없기 때문에 컴파일 단계에서 문법 오류가 난다.
split.cpp
int gInt = 0;
int main()
{
int a = gInt;
return 0;
}
func.cpp
#include "func.h" // int Add(int a, int b)
// {
// return a + b;
// }
int Add(int a, int b)
{
gInt; // 오류
return a + b;
}
`func.cpp`에서 컴파일 오류가 발생한다.
∴ 전역 변수는 해당 파일에서만 사용할 수 있다.
전역 변수를 선언한 헤더 파일을 다른 파일들이 참조하면 발생하는 문제
함수 중복 문제처럼 각각 파일 단위로 컴파일할 때는 문제가 없다.
즉, 개별적인 obj 파일들은 문제가 없다.
하지만 코드를 다 합치는 링킹 단계에서 똑같은 전역 변수가 여러 개 생기기 때문에 문제가 발생한다.
같은 지역에서 같은 지역 변수를 만들면 안 되는 것처럼 전역 변수도 마찬가지다.
func.h
#pragma once
int global = 0; // 전역 변수
func.cpp
#include "func.h" // int global = 0;
int Add(int a, int b)
{
global = 5;
return a + b;
}
split.cpp
#include "func.h" // int global = 0;
// 오류
int main()
{
global = 5; // 오류
int data = Add(10, 20); // 오류
return 0;
}
링킹 단계에서 오류가 발생한다.
이런 문제들을 해결하기 위해 정적 변수와 외부 변수를 사용한다.
참고 링크
• 정적 변수
• 외부 변수
'Programming Language > C++' 카테고리의 다른 글
[C++] 포인터(Pointer), 포인터 배열 (0) | 2024.03.12 |
---|---|
[C++] 운영 체제(OS) (0) | 2024.03.12 |
[C++] 컴파일 과정 (0) | 2024.03.08 |
[C++] 구조체(Structure)[C++ vs. C], 구조체 포인터 (0) | 2024.03.08 |
[C++] 배열(Array) (0) | 2024.03.04 |