[TIL] 24-01-04

Lev·2024년 1월 4일
0

📓TIL Archive

목록 보기
17/33

동적 할당 (이어서)

new와 delete (이어서)

#include <iostream>

class MyInt
{
public:
	int operator+(int _Value)
	{

	}
};

class Sword
{

};

class Player
{
public:
	// Sword Weapon;	// 플레이어와 검은 하나로 붙어있다 (정적)
	Sword* Weapon = nullptr;	// 플레이어는 아직 검을 들고 있지 않다 (동적)

	void CreateSword()
	{
		Weapon = new Sword();	// 플레이어가 검을 만들어 든다 (동적)
	}

	void DeleteSword()	// new를 만들었다면 delete도 반드시 만들자!
	{
		delete Weapon;
	}
};

int main()
{
	_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);

	{
		int Value = 0;
		/*
		위치 : 200번지 (스택영역)
		크기 : 4byte
		형태 : int
		값 : 0
		*/
		Value = 20;	// 스택영역의 200번지에 코드영역의 20이라는 값을 넣으라는 뜻

		int* Ptr = &Value;
		/*
		위치 : 220번지 (스택영역)
		크기 : 8byte
		형태 : int*
		값 : 200번지
		*/
		Ptr = nullptr;	// 220번지를 nullptr로 하라는 뜻
		*Ptr = 1000;	// 200번지에 1000을 넣으라는 뜻
	}
	{
		MyInt NewInt = MyInt();
		NewInt.operator+(/*&NewInt => this, */10);

		new int(10);	// operator new(sizeof(int); => (생략 O)
		// reinterpret_cast<int*>(operator new(sizeof(int))); => (생략 X)
		// 실제론 위와 같은 함수 형태의 연산자이다
		// 스택영역에서 operator new 실행 -> 힙영역의 4byte를 사용하겠다
		// 하지만 리턴값을 받지 않으면 무조건 leak이 되어버린다
		// 해당 메모리에 접근할 수 없기 때문이다
		// int();, Player(); => (X)
		// int Value = int();, Player NewPlayer = Player(); => (O)

		new Player();	// operator new(sizeof(Player));

		new int[10];	// operator new(sizeof(int) * 10);

		int* Ptr = new int(10);
		/*
		Ptr
		위치 : 1000번지 (스택영역 main함수)
		크기 : 8byte
		형태 : int*
		값 : 500번지

		???
		위치 : 500번지 (힙영역)
		크기 : 4byte
		형태 : int
		값 : 10
		*/
	}
	{
		// 동적 할당으로 소유의 개념을 표현할 수 있게 되었다
		Sword NewSword;
		Player NewPlayer;
		
		NewPlayer.Weapon = &NewSword;
	}
}

주의사항

#include <iostream>

class Player
{
public:
	int Hp;

	void MoveMsg()
	{
		printf_s("플레이어가 움직입니다.");
		
		// this -> Hp; => this가 nullptr일 경우 에러
	}
};

void MyDelete(int* _Ptr)
{
	if (nullptr == _Ptr)	// safe delete 형식 (4)
	{
		return;
	}

	_Ptr = nullptr;
	// 이렇게 적어도 함수 밖의 포인터에 영향을 줄 수 없다 (3)
}

int main()
{
	_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF);
	
	// !동적 할당 주의사항!
	{
		// [1. 이미 동적 할당한 곳에 다시 동적 할당 하지 말자]

		int* Ptr = new int();	// 위치 : 500번지
		Ptr = new int();	// 위치 : 600번지 => 500번지에 접근할 방법 잃어버림

		delete Ptr;	// 600번지 delete
		// 4byte leak 발생

		// 포인터는 한번에 하나만 가리킬 수 있다
		// delete는 하나만 삭제해준다
	}
	{
		// [2. 포인터를 사용할 때에는 반드시 방어코드를 치자]

		// null reference exception
		int* Ptr = nullptr;
		*Ptr = 200;	// 0번지에 200을 넣어라...?

		// nullptr 체크
		if (Ptr != nullptr)
		{
			*Ptr = 10;
		}

		// nullptr은 사용할 수 없다
		// int* Ptr = new int() 이런 식으로 동적 할당을 해주었더라도, 방어코드를 적자
		// 자기가 만든건 자기가 테스트해보는 습관을 들이자

		Player* NewPlayer = nullptr;
		NewPlayer->MoveMsg(/*NewPlayer => this*/);	// 가능
        // Q) NewPlayer가 null을 가리키고 있는데 왜 에러가 나지 않나?
		// MoveMsg 함수 내부에서 this를 사용하지 않았기 때문에 에러가 나지 않는다
		
        // 염려된다면 객체를 사용하기 전에 아래와 같은 방어코드가 실행되도록 적어두자
		if (NewPlayer == nullptr)
		{
			return;
		}
	}
	{
		// [3. delete한다고 해도 포인터가 nullptr가 되지는 않는다]

		// dangling pointer
		int* Ptr = new int();	// 500번지(힙영역)을 가리킴
		delete Ptr;	// 500번지의 내용 삭제
		// 하지만 Ptr은 여전히 500번지 또는 힙영역의 어딘가를 가리키고 있다

		int* Ptr = new int();
		MyDelete(Ptr);	// delete Ptr; 과 동일한 의미

		// write access violation
		if (Ptr != nullptr)
		{
			*Ptr = 30;
			// Ptr은 여전히 힙영역의 허공을 가리키고 있기 때문에 작동은 하지만
			// 가리키고 있는 대상이 없기 때문에 에러가 난다
		}

		// 따라서 반드시 아래와 같은 코드를 함께 쳐줘야 한다
		Ptr = nullptr;
	}
	{
		// [4. delete nullptr 하지 말자]

		int* Ptr = nullptr;
		delete Ptr;	// 불가능
		// ...은 아니고 operator delete(Ptr) 함수가 알아서 막아주기는 한다

		// safe delete 코드
		if (Ptr != nullptr)
		{
			delete Ptr;
			Ptr = nullptr;
		}
		// 기본적으로 nullptr을 delete하는 경우를 막기 위해
		// 위와 같은 코드를 꼭 추가해주자
	}
    {
		// [총정리]
		int* Ptr = new int();
		
		if (Ptr == nullptr)		// nullptr 체크
		{
			return;
		}

		*Ptr = 30;
		
		if (Ptr != nullptr)		// safe delete 코드
		{
			delete Ptr;
			Ptr = nullptr;
		}
    }
}

라이브러리

💡 게임은 크게 두가지 요소로 구성되어 있다
엔진 요소 => 물리, 중력, 디버깅, 빛, 렌더링, 메쉬, 이미지
컨텐츠 요소 => 플레이어, 몬스터, 블록, 게임 규칙

이 중 엔진 요소는 다른 게임을 제작할 때 재활용될 가능성이 높다.
이를 위해 재사용될만한 부분을 모아 프로젝트 단위로 관리한다. ⇒ 라이브러리

설정 방법 (Visual Studio)

e.g. Galaga/Galaga.cpp에서 ConsoleEngine/ConsoleScreen.h 사용하기

엔진 프로젝트 = ConsoleEngine
컨텐츠 프로젝트 = Galaga

  1. 엔진 프로젝트 만들기
    • 보통은 처음부터 라이브러리로 만드는 것이 좋다.
    • 하지만 지금은 설정하는 것을 배우기 위해 빈 프로젝트로 만들기
  1. 컨텐츠 프로젝트 ‘포함 디렉터리’ 추가
    Galaga 속성 → 모든 구성, 모든 플랫폼 → VC++ 디렉터리 → 포함 디렉터리 → ..\; 추가 → 적용 → Galaga.cpp에 #include <ConsoleEngine/ConsoleScreen.h> 추가

    • 상위 폴더의 내용도 포함하겠다는 뜻
    • 상위 폴더에 속해 있는 다른 프로젝트의 헤더파일을 추가할 수 있게 된다.
    • 하지만 단순히 선언부인 헤더파일만을 사용하겠다는 뜻이므로, 구현부인 cpp파일을 확인할 수 없어 ‘확인할 수 없는 외부기호’ 에러가 뜬다.
  2. 컨텐츠 프로젝트 ‘참조’ 설정
    Galaga 참조 → ConsoleEngine 추가 → 확인

    • 다른 프로젝트의 내용을 사용하겠다는 뜻
  3. 엔진 프로젝트 ‘라이브러리 프로젝트’ 설정
    ConsoleEngine 속성 → 모든 구성, 모든 플랫폼 → 일반 → 구성 형식 → 정적 라이브러리(.lib) → 적용

    • exe파일로 빌드하지 않겠다는 뜻
    • 실행파일로 만들어지는 것이 아니라, 다른 프로젝트에 이용할 목적이기 때문이다.
  4. 솔루션의 ‘프로젝트 종속성’ 설정
    솔루션 속성 → 프로젝트 종속성 → 프로젝트 : Galaga → 다음에 종속 : ConsoleEngine

    • 엔진 프로젝트가 생성된 후 컨텐츠 프로젝트가 생성되어야 제대로 동작할 수 있다는 뜻
    • 프로젝트간의 빌드 순서를 결정하기 위해 설정해준다.
    • 클래스의 상속관계와 비슷하다.
  5. 제대로 작동하는지 테스트하기

// [ConsoleEngine/ConsoleScreen.h]
#pragma once

class ConsoleScreen
{
public:
	void Test();
};
// [ConsoleEngine/ConsoleScreen.cpp]
#include "ConsoleScreen.h"
#include <iostream>

void ConsoleScreen::Test()
{
	printf_s("라이브러리 테스트");
}
// [Galaga/Galaga.cpp]
#include <ConsoleEngine/ConsoleScreen.h>

int main()
{
	ConsoleScreen NewScreen = ConsoleScreen();

	NewScreen.Test();	// 라이브러리 테스트
}
profile
⋆꙳⊹⋰ 𓇼⋆ 𝑻𝑰𝑳 𝑨𝑹𝑪𝑯𝑰𝑽𝑬 ⸝·⸝⋆꙳⊹⋰

0개의 댓글