정의란?

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 파일에서 인클루드 해서 사용한다.

중복 정의를 할 수 있기 때문에 중복된 정의가 다른 경우 문제가 발생할 수 도 있다.

클래스 크기는 같지만, 멤버 함수 순서가 달라 메모리 할당 순서가 바뀌는 경우

 

+ Recent posts