예제 8. 동적 할당 문제

// ~~
		switch (rand() % 3)
		{
			case 0:
			{
				p = new Knight();
				p->_hp = 100;
				p->_attack = 100;
				break;
			}	
			case 1:
			{
				// 여기서 같이 만들어준다
				// 이런 저런 펫 정보 추가될 예정
				Pet pet;

				// Archer를 만들 때 pet 정보도 넘겨준다
				p = new Archer(&pet);
				p->_hp = 100;
				p->_attack = 100;

				break;
			}		
			case 2:
			{
				p = new Mage();
				p->_hp = 100;
				p->_attack = 100;
				break;
			}			
		}
        //~~ Exercise_8.cpp

이번엔 펫이 Archer(pet) 생성자를 통해 만들어진다.

#include "Archer.h"
#include "Pet.h"

Archer::Archer(Pet* pet) : _pet(pet)
{
	
}

Archer::Archer(int hp) : Player(hp)
{
}

Archer::~Archer()
{
	delete _pet;
}

// Archer.cpp

스택 메모리 영역에 pet 객체가 생성된다.

pet의 주소를 아쳐에게 전달하고 아쳐가 제거될 때 pet이 제거되고 있다.

즉 지역 변수로 관리되고 있는 pet이 계속 지워지고 있는 문제이다.

			{
				Pet* pet = new Pet;

				p = new Archer(pet);
				p->_hp = 100;
				p->_attack = 100;

				break;
			}

따라서 아쳐를 생성할 때 pet을 동적 할당하게 설정한다.

 

예제 9. 더블 프리(Double Free) 문제

int main()
{
	srand(static_cast<unsigned int>(time(nullptr)));

	Archer* archer = new Archer(new Pet());
	archer->_hp = 100;
	archer->_maxHp = 100;
	archer->_attack = 20;

	Knight* knight = new Knight();	
	knight->_hp = 150;
	knight->_maxHp = 150;
	knight->_attack = 100;
	
	int damage = knight->GetAttackDamage();
	archer->AddHp(-damage);

	delete archer;
	delete knight;
}

 

위에서 아쳐에게 펫을 만들어주고 기사와 싸우게 만든다.

#include "Archer.h"
#include "Pet.h"

Archer::Archer(Pet* pet) : _pet(pet)
{
	
}

Archer::Archer(int hp) : Player(hp)
{
}

Archer::~Archer()
{
	if (_pet != nullptr)
		delete _pet;
}

void Archer::AddHp(int value)
{
	Player::AddHp(value);

	if (IsDead())
	{
		delete _pet;
	}
}

이후 데미지를 계산해서 죽으면 펫을 제거하고 아쳐 객체를 delete 하는 데에서 크래시가 발생한다.

아쳐가 죽을 때 pet이 제거되고 아쳐의 소멸자가 호출될 때 또 pet이 제거되기 때문이며 이는 더블 프리 문제이다.

void Archer::AddHp(int value)
{
	Player::AddHp(value);

	if (IsDead())
	{
		delete _pet;
		_pet = nullptr;
	}
}

따라서 포인터를 0으로 만들어주고 위의 Null Check를 통과하게 해서 더블 프리를 하지 않게 한다.

 

예제 10. Use-After-Free ; 잘못된 메모리 참조(투사체 문제)

	Arrow* arrows[10] = {};
	for (int i = 0; i < 10; i++)
	{
		// 기사를 타겟으로, 궁수의 공격력을 지닌 상태
		Arrow* arrow = new Arrow(knight, archer->_attack);
		arrows[i] = arrow;
	}

	for (int i = 0; i < 10; i++)
	{
		arrows[i]->AttackTarget();

		// 기사가 죽었으면 소멸시켜준다
		if (knight != nullptr)
		{
			if (knight->IsDead())
			{
				delete knight;
				knight = nullptr;
			}
		}	

		delete arrows[i];
		arrows[i] = nullptr;
	}

화살이 8발 째 발사될 때 이미 기사는 제거된 상태(객체가 제거되어 메모리가 free된 상태)이다.

하지만 여전히 8발 째, 9발 , 10발 째 화살이 원래 기사를 가리키고 있었기 때문에 발생하는 문제이다.

기사의 PrintInfo는 virtual이 붙어있기 때문에 가상 함수 테이블을 저장하고 있었다. 메모리에 할당된 기사의 영역이 제거되면서 화살이 그 영역을 가리키고 있었다.

따라서 기사가 죽었을 때 소멸시켜주는 부분을 삭제하고 반복문 이후에 아쳐와 기사를 제거해준다.

'기초 C++ 스터디 > 예제' 카테고리의 다른 글

9-5. STL - List 구현하기(실습)  (0) 2023.06.14
9-3. STL - Vector 구현하기 (실습)  (0) 2023.06.14
7-2. 디버깅 예제 #5 ~ 7  (0) 2023.06.07
7-1. 디버깅 예제 #1 ~ 4  (0) 2023.06.07
7-0. 디버깅 연습  (0) 2023.06.07