정의란?
C++에서 정의는 객체를 "실체"로 만들겠다는 뜻이다.
실체로 만들겠다 라는 것은 메모리에 할당하는 것을 의미한다. 메모리 상에 유효한 비트로 만들겠다는 것이다.
#include <iostream>
class CTest;
int g_Val;
int g_Arr[3];
CTest g_T;
int main()
{
int v;
int arr[3];
}
int Add(int a, int b)
{
return a + b;
}
main 위에 정의한 전역변수는 데이터 영역에 , main 안에 있는 지역변수 스택 영역에 , Add함수 자체도 코드영역에 들어가기 때문에 모두 실체화 한거라고 볼 수 있고, 정의했다고 말할 수 있다.
이런 변수나 함수는 프로세스가 사용하는 가상 메모리이다.
class CTest
{
public:
int m_Value;
int MFunc();
}
이런 클래스 정의 자체는 프로세스와 상관 없이 컴파일러가 사용하는 메모리이다.
(함수도 같은거 아닌가? 둘 다 코드영역 아닌가? 함수는 포인터 형태로 프로세스가 사용하기 때문에 성격이 다른건가? 클래스 코드 자체는 컴파일 할 때만 참고하기 때문에 성격이 다르다고 하는건가?)
선언이란?
위에서 봤던 클래스, 전역변수, 지역변수, 함수 모두 선언한 것이다.
정의라고 해놓고 또 선언이란다. 무슨말이냐?
이유는 정의는 선언을 포함하기 때문이다. 정의를 한 순간 해당 개체는 알려지므로(선언되므로) 따로 선언해 줄 필요가 없다.
그럼 정의만 있어도 되는 거 아니냐?
선언이 꼭 필요한 경우가 있음.
// A.cpp
int g_Val;
int Func()
{
return 1;
}
// Main.cpp
void main()
{
g_Val = Func();
}
이렇게 사용하면 컴파일 에러가 발생한다.
g_Val과 Func()는 정의와 동시에 선언 되었지만, A.cpp 에 한정된다.
main도 g_Val과 Func()에 대해 알 수 있게 해주어야 한다.
// Main.cpp
extern int g_Val;
int Func();
void main()
{
g_Val = Func();
}
따로 헤더파일을 만들어 include를 해주지 않아도, 각 cpp 파일이 obj파일로 만들어지고 심볼릭 테이블에서 정의를 찾아 매핑해주기 때문에 이 코드로도 컴파일/실행이 가능하다.
#include "A.cpp"
void main()
{
g_Val = Func();
}
이렇게 하면 되는거 아니야?
아니다. 링킹 오류가 난다.
include는 전처리과정에서 복붙된다.
그래서 최종 obj 파일을 링킹할 때 정의가 2개씩 존재하기 때문에 링커가 선택할 수 없다.
extern & static
extern 외부에서 사용할 수 있음을 알려주고, static은 사용 범위가 한정된다는 의미를 알려준다.
extern은 선언할 때만 사용하는 것이 아니라, 정의할 때도 사용된다.
extern int a; // 선언만 됨.
extern int b = 3; // 선언과 정의가 동시에 됨.
int c; // 선언과 정의가 동시에 됨.
int d = 3; // 선언과 정의가 동시에 됨.
일반적으로 `extern int b = 3;`은 `int b = 3;`과 의미가 똑같기 때문에 생략한다.
// A.cpp
extern int g_Val;
// Main.cpp
#include <iostream>
extern int g_Val;
void main()
{
printf("%d", g_Val);
}
그래서 이렇게 선언만 있는 경우, 링킹 오류가 발생한다. 심볼을 찾을 수 없다고 나온다.
클래스 선언과 정의
// A.cpp
class CTest
{
public:
int m_Val;
};
// Main.cpp
class CTest; // 선언
void main()
{
CTest t; // Compile Error
}
12 라인에서 컴파일 에러가 난다.
왜일까?
값형은 스택에 할당되고, 스택의 크기는 컴파일 시점에 결정된다.
CTest는 Main.cpp 입장에서는 선언만 돼있다. 어떤 멤버가 있는지, 크기가 몇인지는 알 수 없다.
그렇기 때문에 힙에 할당되는 포인터 형은 가능하다.
class CTest;
void main()
{
CTest* pT;
}
사실 힙이라기보단 포인터 타입이 OS에 따라 4바이트 혹은 8바이트로 정의되어있기 때문이라고 할 수 있겠다.
해결 방법은?
Main.cpp에도 CTest를 정의해주면 된다. 클래스는 중복 정의를 허용한다.
그래서 보통 헤더 파일에 클래스 정의를 만들고, cpp 파일에서 인클루드 해서 사용한다.
중복 정의를 할 수 있기 때문에 중복된 정의가 다른 경우 문제가 발생할 수 도 있다.
클래스 크기는 같지만, 멤버 함수 순서가 달라 메모리 할당 순서가 바뀌는 경우
'Programming > 언어' 카테고리의 다른 글
[Kotlin] Kotlin의 Null Safety. ? / ?. / ?: / !! 가 무엇일까? (1) | 2023.10.22 |
---|---|
[Kotlin] Coroutine 기본 개념, 동작 원리 (0) | 2023.03.19 |
[Kotlin] Delegation (0) | 2023.03.01 |
[Kotlin] object, companion object (0) | 2023.02.26 |
[Kotlin] Generics 공변성, 반공변성(out, in) (0) | 2023.02.26 |