attack 값이 잘못된 쓰레기값(음수)로 출력됩니다.attack 값이 기본값 10으로 설정되는 것입니다.Knight)멤버 변수:
_hp: 기사의 생명력 (기본값 100)._attack: 기사의 공격력 (기본값 10).생성자:
_hp를 100, _attack을 10으로 초기화.hp를 입력값으로 받아 설정. 하지만 _attack 초기화 누락.멤버 함수:
PrintInfo: hp와 attack 값을 출력.Knight(int hp)에서 _attack 값을 초기화하지 않았습니다._attack은 초기화되지 않은 채로 메모리에 저장된 쓰레기값을 가집니다.2번 기사의 _attack이 음수(쓰레기값)로 출력됩니다.Knight::Knight(int hp) : _hp(hp)
{
// _attack 초기화가 누락됨
}
_attack 값을 초기화하지 않았으므로, 해당 멤버 변수는 메모리에 저장된 쓰레기값을 유지합니다.생성자 초기화 리스트에 _attack 추가
Knight::Knight(int hp) : _hp(hp), _attack(10)
{
// _attack도 기본값으로 초기화됨
}
멤버 변수 선언 시 초기화 (C++11 이상)
class Knight {
public:
int _hp = 100;
int _attack = 10; // 기본값 설정
};
생성자 내부에서 초기화
Knight::Knight(int hp)
{
_hp = hp;
_attack = 10; // 초기화 추가
}
헤더 파일 (Knight.h)
#pragma once
class Knight
{
public:
Knight();
Knight(int hp);
~Knight();
void PrintInfo();
public:
int _hp = 100; // 기본값 초기화
int _attack = 10; // 기본값 초기화
};
구현 파일 (Knight.cpp)
#include "Knight.h"
#include <iostream>
using namespace std;
// 기본 생성자
Knight::Knight() : _hp(100), _attack(10) {}
// 오버로드된 생성자
Knight::Knight(int hp) : _hp(hp), _attack(10) {} // _attack 초기화 추가
// 소멸자
Knight::~Knight() {}
// 정보 출력 함수
void Knight::PrintInfo()
{
cout << "HP: " << _hp << endl;
cout << "ATT: " << _attack << endl;
}
메인 파일 (main.cpp)
#include <iostream>
using namespace std;
#include "Knight.h"
int main()
{
// 기본값 생성
Knight* k1 = new Knight();
k1->PrintInfo();
// HP 200으로 설정
Knight* k2 = new Knight(200);
k2->PrintInfo();
delete k1;
delete k2;
return 0;
}
HP: 100
ATT: 10
HP: 200
ATT: 10
HP=100, ATT=10) 출력.HP=200, ATT=10 출력._attack의 초기화 문제가 해결되었습니다.메모리 초기화 상태 확인:
_attack의 초기값 확인 가능 (보통 0xcdcdcdcd 등 쓰레기값).정적 분석 도구 사용:
디버깅 포인트 추가:
cout << "Debug: _attack = " << _attack << endl;
Knight 객체 10개를 생성한 후 출력하려 할 때 크래시가 발생합니다.<=)로 인해, 배열의 유효하지 않은 인덱스에 접근하고 있습니다.const int KNIGHT_COUNT = 10;
int main()
{
Knight* knights[KNIGHT_COUNT];
for (int i = 0; i < KNIGHT_COUNT; i++) // 10개의 Knight 객체 생성
{
knights[i] = new Knight();
}
for (int i = 0; i <= KNIGHT_COUNT; i++) // 잘못된 반복 조건 (<=)
{
knights[i]->PrintInfo(); // 유효하지 않은 인덱스 접근 시도
delete knights[i]; // 메모리 해제
}
}
배열 크기와 유효 범위:
knights의 크기는 KNIGHT_COUNT = 10으로 선언되어 있으며, 유효한 인덱스는 0 ~ 9입니다.for문에서 조건 i <= KNIGHT_COUNT는 i = 10까지 반복을 시도하며 유효 범위를 초과합니다.결과:
knights[10]은 유효하지 않은 메모리 공간에 접근합니다.knights[10]->PrintInfo()에서 문제가 발생했음을 확인할 수 있습니다.knights 배열의 메모리 상태를 확인합니다.knights[10]는 배열 크기를 초과했으므로 쓰레기값(또는 nullptr)를 가리키게 됩니다.for (int i = 0; i < KNIGHT_COUNT; i++) // 올바른 반복 조건
{
knights[i]->PrintInfo();
delete knights[i];
}
i < KNIGHT_COUNT로 수정하여, 배열의 유효한 인덱스(0 ~ 9)만 접근하도록 합니다.Knight.h):#pragma once
class Knight
{
public:
Knight();
Knight(int hp);
~Knight();
void PrintInfo();
public:
int _hp;
int _attack;
};
Knight.cpp):#include "Knight.h"
#include <iostream>
using namespace std;
// 기본 생성자
Knight::Knight() : _hp(100), _attack(10) {}
// 오버로드된 생성자
Knight::Knight(int hp) : _hp(hp), _attack(10) {}
// 소멸자
Knight::~Knight() {}
// 정보 출력 함수
void Knight::PrintInfo()
{
cout << "HP: " << _hp << endl;
cout << "ATT: " << _attack << endl;
}
main.cpp):#include <iostream>
using namespace std;
#include "Knight.h"
const int KNIGHT_COUNT = 10;
int main()
{
// Knight 포인터 배열
Knight* knights[KNIGHT_COUNT];
// Knight 객체 생성
for (int i = 0; i < KNIGHT_COUNT; i++)
{
knights[i] = new Knight();
}
// Knight 객체 출력 및 메모리 해제
for (int i = 0; i < KNIGHT_COUNT; i++) // 수정된 반복 조건
{
knights[i]->PrintInfo();
delete knights[i];
}
return 0;
}
HP: 100
ATT: 10
HP: 100
ATT: 10
...
HP: 100
ATT: 10
Knight 객체의 정보가 정상적으로 출력되며, 크래시 없이 프로그램이 종료됩니다.배열 범위 검사 도구 활용
디버그 출력
for (int i = 0; i <= KNIGHT_COUNT; i++) // 원래 반복 조건
{
cout << "Debug: i = " << i << endl;
knights[i]->PrintInfo(); // 크래시 발생 지점
}
Knight 클래스에서 피격 데미지 실험 중,
Knight2가Knight1을 공격하여 한방에 처치하도록 설계했지만, "죽었다"는 로그가 출력되지 않는 문제가 발생했습니다. 코드를 한 줄씩 분석하여 원인을 찾고, 해결 방법을 제시하겠습니다.
main 함수int main()
{
Knight* k1 = new Knight();
k1->_hp = 100;
k1->_attack = 10;
Knight* k2 = new Knight();
k2->_hp = 2000;
k2->_attack = 200;
int damage = k2->_attack;
k1->AddHp(-damage);
if (k1->IsDead())
{
cout << "죽었습니다!" << endl;
}
else
{
cout << "엥? 살았습니다!" << endl;
}
delete k1;
delete k2;
}
Knight* k1 및 Knight* k2 생성:
k1은 기본값으로 생성된 Knight 객체이며, hp와 attack 값을 수동으로 초기화합니다.k1->_hp = 100;
k1->_attack = 10;k2 역시 동일하게 생성되어 hp와 attack 값을 설정합니다.k2->_hp = 2000;
k2->_attack = 200;데미지 계산 및 적용:
k2의 공격력을 k1의 체력에서 차감합니다.int damage = k2->_attack;
k1->AddHp(-damage);AddHp 함수는 k1의 체력에서 200만큼 감소시킵니다.IsDead 함수 호출:
k1->IsDead() 함수로 k1이 사망 상태인지 확인합니다.if (k1->IsDead())객체 해제:
delete k1 및 delete k2를 호출하여 동적으로 할당된 메모리를 해제합니다.Knight 클래스 정의#pragma once
class Knight
{
public:
Knight();
Knight(int hp);
~Knight();
void PrintInfo();
void AddHp(int value);
bool IsDead();
public:
int _hp;
int _attack;
};
멤버 변수:
_hp: 기사의 체력을 나타냅니다._attack: 기사의 공격력을 나타냅니다.생성자:
hp와 attack을 각각 100과 10으로 초기화합니다.hp를 설정하고 attack은 기본값 10으로 초기화합니다.멤버 함수:
AddHp: 체력 값을 증가 또는 감소시킵니다.IsDead: 체력이 0인지 확인하여 생존 여부를 반환합니다.PrintInfo: 체력과 공격력을 출력합니다.Knight 클래스 멤버 함수 구현void Knight::AddHp(int value)
{
_hp += value;
}
bool Knight::IsDead()
{
return (_hp == 0); // 기존 코드
}
AddHp 함수:
value를 _hp에 더합니다.IsDead 함수:
0일 때만 true를 반환합니다. 현재 코드는 <= 조건을 누락하여, 체력이 0 이하로 떨어져도 false를 반환할 수 있습니다.IsDead 함수에서 체력이 0일 때만 사망 상태를 반환하도록 구현되었습니다. 그러나 피격 후 k1의 체력은 -100이므로 IsDead가 false를 반환하고, "엥? 살았습니다!" 메시지가 출력됩니다.
IsDead 함수의 조건을 수정하여, 체력이 0 이하인 경우에도 true를 반환하도록 변경합니다.
bool Knight::IsDead()
{
return (_hp <= 0); // 수정된 코드
}
k1->AddHp(-200) 호출 후, k1->_hp는 -100이 됩니다.IsDead 함수가 true를 반환하므로 "죽었습니다!" 메시지가 출력됩니다.bool Knight::IsDead()
{
return (_hp <= 0); // 수정된 조건
}
int main()
{
Knight* k1 = new Knight();
k1->_hp = 100;
k1->_attack = 10;
Knight* k2 = new Knight();
k2->_hp = 2000;
k2->_attack = 200;
int damage = k2->_attack;
k1->AddHp(-damage);
if (k1->IsDead())
{
cout << "죽었습니다!" << endl;
}
else
{
cout << "엥? 살았습니다!" << endl;
}
delete k1;
delete k2;
}
"생명력 구슬" 아이템을 사용해 체력을 랜덤하게 회복하도록 구현했으나, 테스트 과정에서 체력을 충분히 채운 후에도 캐릭터가 죽는 현상이 발생했습니다. 이 문제는 HP 값이 정수의 범위를 넘어서는 오버플로우로 인한 것으로 보입니다.
main() 함수Knight* k1 = new Knight();
k1->_hp = 100;
k1->_attack = 10;
Knight 객체를 생성하고, 체력(_hp)과 공격력(_attack)을 초기화합니다.100, 공격력은 10으로 설정됩니다.const int TEST_COUNT = 100 * 10000; // 100만
const int TEST_VALUE = 100 * 10000; // 100만
TEST_COUNT: 체력 증가 작업을 반복하는 횟수. 100만 번 수행합니다.TEST_VALUE: 한 번에 증가시킬 체력 값. 100만으로 설정되어 있습니다.for (int i = 0; i < TEST_COUNT; i++)
{
k1->AddHp(TEST_VALUE);
}
AddHp 함수를 호출하여 체력을 증가시킵니다.int의 최대값(약 21억)을 초과할 경우 오버플로우가 발생할 수 있다는 점입니다.if (k1->IsDead())
{
cout << "죽었습니다!" << endl;
}
else
{
cout << "엥? 살았습니다!" << endl;
}
IsDead() 함수는 체력이 0 이하인지 확인합니다._hp가 음수로 변환되면, IsDead() 함수가 잘못된 결과를 반환할 수 있습니다.delete k1;
Knight 객체를 해제합니다.AddHp() 함수void Knight::AddHp(int value)
{
_hp += value;
}
_hp가 int 타입으로 선언되어 있어, 오버플로우가 발생하면 음수로 변환됩니다.IsDead() 함수bool Knight::IsDead()
{
return (_hp <= 0);
}
0 이하인지 확인합니다._hp가 음수가 되어 잘못된 결과를 반환할 수 있습니다.오버플로우 발생:
_hp가 int 타입으로 선언되어 있어, 최대값(약 21억)을 초과하면 음수로 변환됩니다.long long 또는 double)으로 변경해야 합니다.테스트 반복 횟수:
TEST_COUNT와 TEST_VALUE가 모두 매우 큰 값으로 설정되어 있어, 반복적으로 체력을 증가시키는 동안 오버플로우가 발생할 가능성이 큽니다._hp의 타입을 double 또는 long long으로 변경하여 더 큰 범위를 제공.double _hp;
void Knight::AddHp(int value)
{
_hp += value;
if (_hp > 100)
{
_hp = 100;
}
}
Knight.h#pragma once
class Knight
{
public:
Knight();
Knight(int hp);
~Knight();
void PrintInfo();
void AddHp(int value);
bool IsDead();
public:
double _hp; // 타입 변경
int _attack;
};
Knight.cpp#include "Knight.h"
#include <iostream>
using namespace std;
Knight::Knight() : _hp(100), _attack(10) {}
Knight::Knight(int hp) : _hp(hp), _attack(10) {}
Knight::~Knight() {}
void Knight::AddHp(int value)
{
_hp += value;
if (_hp > 100) // 최대 체력 제한
{
_hp = 100;
}
}
bool Knight::IsDead()
{
return (_hp <= 0);
}
void Knight::PrintInfo()
{
cout << "HP: " << _hp << endl;
cout << "ATT: " << _attack << endl;
}
main.cpp#include <iostream>
using namespace std;
#include "Knight.h"
int main()
{
Knight* k1 = new Knight();
k1->_hp = 100;
k1->_attack = 10;
const int TEST_COUNT = 100 * 10000; // 100만
const int TEST_VALUE = 100 * 10000; // 100만
for (int i = 0; i < TEST_COUNT; i++)
{
k1->AddHp(TEST_VALUE);
}
if (k1->IsDead())
{
cout << "죽었습니다!" << endl;
}
else
{
cout << "엥? 살았습니다!" << endl;
}
delete k1;
}
hp 값이 0일 때 정수를 0으로 나누는 예외 발생.main 함수int main()
{
Knight* k1 = new Knight();
k1->_hp = 100;
k1->_maxHp = 100;
k1->_attack = 100;
Knight* k2 = new Knight();
k2->_hp = 100;
k2->_maxHp = 100;
k2->_attack = 100;
int damage = k1->GetAttackDamage();
k2->AddHp(-damage);
int damage2 = k2->GetAttackDamage();
k1->AddHp(-damage2);
delete k1;
delete k2;
}
_hp = 100, _maxHp = 100, _attack = 100.k1->GetAttackDamage()를 호출해 k2에게 데미지 부여.k2->GetAttackDamage()를 호출해 k1에게 데미지 부여.GetAttackDamage 함수int Knight::GetAttackDamage()
{
// hp 50% 이하 => maxHp / hp가 2 이상
int damage = _attack;
int ratio = _maxHp / _hp;
if (ratio > 2)
damage *= 2;
return damage;
}
_attack 초기화.ratio = _maxHp / _hp 계산:hp가 0이 될 경우 정수를 0으로 나누는 예외 발생.ratio > 2이면 데미지 2배 적용.AddHp 함수void Knight::AddHp(int value)
{
_hp += value;
if (_hp < 0)
_hp = 0;
if (_hp > _maxHp)
_hp = _maxHp;
}
_hp 값이 0 이하가 되면 _hp = 0으로 고정._hp 값이 최대 체력 초과 시 _hp = _maxHp로 고정.GetAttackDamage 함수의 ratio 계산 문제:hp가 0일 때, int ratio = _maxHp / _hp에서 0으로 나눔.hp == 0 예외 처리 추가:
hp가 0일 때 ratio를 계산하지 않고 바로 데미지를 리턴.
수정 코드:
int Knight::GetAttackDamage()
{
int damage = _attack;
if (_hp == 0) // hp가 0일 때 예외 처리
return damage;
int ratio = _maxHp / _hp;
if (ratio > 2)
damage *= 2;
return damage;
}
조건문으로 percentage를 활용한 논리 변경:
비율 계산을 정수형 퍼센트(%)로 변환.
수정 코드:
int Knight::GetAttackDamage()
{
int damage = _attack;
if (_maxHp == 0) // maxHp가 0인 경우 기본 데미지 반환
return damage;
int percentage = (_hp * 100) / _maxHp; // 체력을 퍼센트로 계산
if (percentage <= 50) // 체력이 50% 이하일 경우
damage *= 2;
return damage;
}
에러 메시지 확인:
GetAttackDamage 함수의 ratio 계산 부분에서 발생.조사식 및 조건부 중단점 설정:
_hp가 0인 경우를 확인하기 위해 조건부 중단점 설정.k1->_hp == 0 or k2->_hp == 0.수정 후 결과 확인:
hp == 0 상태에서 예외 없이 정상 동작 확인.GetAttackDamage 함수int Knight::GetAttackDamage()
{
int damage = _attack;
if (_hp == 0) // hp가 0일 경우 예외 처리
return damage;
if (_maxHp == 0) // maxHp가 0일 경우 기본 데미지 반환
return damage;
int percentage = (_hp * 100) / _maxHp; // 체력을 퍼센트로 계산
if (percentage <= 50) // 체력이 50% 이하일 경우
damage *= 2;
return damage;
}
hp == 0일 때도 프로그램이 크래시 없이 정상 작동.void Knight::OnDamaged(Knight* attacker) {
if (attacker == nullptr)
return;
// 체력 감소 처리
int damage = attacker->GetAttackDamage();
AddHp(-damage);
// 무한 반격이 발생하는 코드
attacker->OnDamaged(this);
}
문제점:
1. attacker->OnDamaged(this)가 재귀적으로 호출되며 무한히 반복됩니다.
2. 체력 처리가 완료되었음에도 죽거나 무효화 조건 없이 계속 반격이 발생합니다.
체력이 0 이하인지 확인:
데미지가 0일 경우 처리:
void Knight::OnDamaged(Knight* attacker) {
if (attacker == nullptr)
return;
// 공격 받음
int damage = attacker->GetAttackDamage();
AddHp(-damage);
// 데미지가 0이거나 이미 죽었다면 종료
if (damage == 0 || IsDead())
return;
// 반격 처리
attacker->OnDamaged(this);
}
수정 후 추가된 조건:
1. damage == 0:
IsDead():스택 오버플로우 확인:
OnDamaged 함수가 무한 호출되었음을 확인.추적과 조사식 활용:
게임에서 다수의 클래스(Knight, Archer, Mage)를 관리하기 위해 공통 부모 클래스 Player를 사용하도록 설계했습니다. 추가적으로 Archer 클래스는 펫(Pet)을 가지며, 이를 관리하는 로직이 포함되어 있습니다. 문제는 플레이어를 생성하고 삭제하는 루프에서 메모리 누수가 발생하여 프로그램이 크래시하는 상황입니다.
소멸자에서 가상 함수(virtual) 키워드 누락
Player 클래스의 소멸자가 가상 함수로 선언되지 않았습니다. 따라서 delete p;를 호출했을 때, 파생 클래스의 소멸자가 호출되지 않습니다.Archer 클래스의 _pet 객체가 삭제되지 않고 메모리 누수가 발생합니다.Archer 클래스의 Pet 관리
Pet은 new를 통해 동적 할당되지만, 부모 클래스의 소멸자가 호출될 때 delete되지 않기 때문에 메모리 누수가 점점 증가합니다.플레이어 객체 생성
Knight, Archer, Mage 중 하나를 랜덤으로 생성.new를 통해 동적 메모리로 할당됩니다.플레이어 객체 삭제
delete 키워드로 플레이어 객체 삭제.메모리 누수
Archer 클래스의 _pet 멤버는 동적으로 할당된 메모리가 해제되지 않아 메모리 누수가 발생.Player 소멸자에 virtual 키워드 추가
virtual ~Player();
delete 시 파생 클래스 소멸자가 제대로 호출됩니다.Archer 클래스의 소멸자에서 _pet 해제
_pet을 delete하고 있으므로, 수정 없이 유지.추가 테스트
Player.h 수정#pragma once
class Player
{
public:
Player();
Player(int hp);
virtual ~Player(); // 가상 소멸자 추가
void PrintInfo();
void AddHp(int value);
bool IsDead();
int GetAttackDamage();
void OnDamaged(Player* attacker);
public:
int _hp;
int _maxHp;
int _attack;
};
Player.cpp 유지변경 없음.
Archer.h 유지#pragma once
#include "Player.h"
class Archer : public Player
{
public:
Archer();
Archer(int hp);
~Archer();
public:
class Pet* _pet;
};
Archer.cpp 유지Archer의 소멸자가 제대로 호출되기 때문에 기존 로직을 유지합니다.
int main()
{
srand(static_cast<unsigned int>(time(nullptr)));
while (true)
{
Player* p = nullptr;
switch (rand() % 3)
{
case 0:
p = new Knight();
p->_hp = 100;
p->_attack = 100;
break;
case 1:
p = new Archer();
p->_hp = 100;
p->_attack = 100;
break;
case 2:
p = new Mage();
p->_hp = 100;
p->_attack = 100;
break;
}
delete p;
}
}
Archer 생성자에서 생성하다가, 사장님의 요청으로 펫 생성 로직을 메인 함수로 옮겼습니다.switch (rand() % 3)
{
case 0:
p = new Knight();
p->_hp = 100;
p->_attack = 100;
break;
case 1:
Pet pet; // 지역 변수로 펫 생성
p = new Archer(&pet); // 펫의 주소를 넘김
p->_hp = 100;
p->_attack = 100;
break;
case 2:
p = new Mage();
p->_hp = 100;
p->_attack = 100;
break;
}
delete p; // Player 객체 삭제
case 1: Pet pet을 생성합니다. 이는 스택 메모리 영역에 저장됩니다.new Archer(&pet)로 펫 객체의 주소를 넘겨줍니다.Pet 객체는 함수가 종료되면 소멸합니다. 따라서 포인터가 소멸된 메모리를 가리켜 크래시가 발생합니다.Archer 클래스Archer::Archer(Pet* pet) : _pet(pet) {}
Archer::~Archer()
{
if (_pet != nullptr)
delete _pet; // 포인터로 가리키는 객체 삭제
}
_pet은 new로 동적 생성된 객체를 가리켜야 합니다. 하지만 스택 변수의 주소를 받으면 소멸 시 delete로 스택 메모리를 해제하려고 시도합니다.Pet pet의 주소를 넘겨줍니다.delete를 호출합니다.switch (rand() % 3)
{
case 0:
p = new Knight();
p->_hp = 100;
p->_attack = 100;
break;
case 1:
p = new Archer(new Pet()); // 동적 메모리로 Pet 생성
p->_hp = 100;
p->_attack = 100;
break;
case 2:
p = new Mage();
p->_hp = 100;
p->_attack = 100;
break;
}
delete p; // Player 객체와 내부 펫 메모리 해제
가상 소멸자 추가
Player의 소멸자에 virtual 키워드를 추가하여, 자식 클래스의 소멸자가 호출되도록 보장합니다.virtual ~Player();
스마트 포인터 도입
std::unique_ptr 또는 std::shared_ptr를 사용합니다.std::unique_ptr<Pet> _pet;
문제 상황:
궁수가 죽으면 펫(Pet)이 같이 죽도록 구현한 코드에서 프로그램이 크래시가 발생했습니다.
double free 에러, 즉 메모리를 두 번 해제해서 발생한 문제입니다.
목표:
크래시의 원인을 찾아 수정하여 프로그램이 정상적으로 작동하도록 만들기.
main 함수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;
코드 흐름:
Archer)를 생성합니다.Pet)도 함께 생성되어 Archer 생성자로 전달됩니다.AddHp(-damage)).delete _pet).delete archer, delete knight).문제의 원인:
AddHp에서 펫을 삭제합니다.Archer 객체가 삭제될 때, 소멸자에서 delete _pet을 다시 호출하여 펫이 두 번 삭제됩니다.Archer 클래스class Archer : public Player {
public:
Archer(Pet* pet);
~Archer();
virtual void AddHp(int value);
public:
Pet* _pet;
};
주요 코드:
생성자:
Archer::Archer(Pet* pet) : _pet(pet) {}
_pet에 저장.소멸자:
Archer::~Archer() {
if (_pet != nullptr) delete _pet;
}
_pet이 nullptr이 아니면 삭제.AddHp 함수:
void Archer::AddHp(int value) {
Player::AddHp(value);
if (IsDead()) {
delete _pet;
}
}
문제의 흐름:
AddHp에서 궁수가 죽었을 때(IsDead()), delete _pet을 실행하여 펫을 삭제합니다.Archer 객체가 삭제될 때 소멸자에서 또 한 번 delete _pet이 실행됩니다.문제의 원인:
AddHp 함수에서 delete _pet을 실행한 후 _pet을 nullptr로 설정하지 않아서 생긴 문제.문제 상황 요약:
수정 코드:
void Archer::AddHp(int value) {
Player::AddHp(value);
if (IsDead()) {
delete _pet;
_pet = nullptr; // 삭제 후 nullptr로 설정
}
}
설명:
delete _pet 이후 _pet을 nullptr로 설정하여 이후 추가 삭제를 방지합니다._pet이 nullptr인지 확인 후 삭제를 실행.소멸자에서도 안전장치 추가:
Archer::~Archer() {
if (_pet != nullptr) {
delete _pet;
_pet = nullptr; // 추가로 nullptr로 설정
}
}
AddHp 함수에서 삭제와 함께 초기화:
void Archer::AddHp(int value) {
Player::AddHp(value);
if (IsDead()) {
delete _pet;
_pet = nullptr;
}
}
설명:
AddHp에서 펫을 삭제하고 nullptr로 설정._pet을 확인.void Archer::AddHp(int value) {
Player::AddHp(value);
if (IsDead()) {
delete _pet;
_pet = nullptr; // 삭제 후 nullptr로 설정
}
}
Archer::~Archer() {
if (_pet != nullptr) {
delete _pet;
_pet = nullptr; // 추가 안전장치
}
}
이 코드는 궁수와 화살을 사용해 기사를 공격하는 기능을 구현하고 있습니다. 화살(Arrow) 클래스는 타겟(_target)을 관리하며, 타겟의 체력을 공격합니다. 테스트 중에 기사(Knight)가 죽었을 때, 죽은 기사를 타겟으로 화살이 계속 공격을 시도하면서 프로그램이 크래시(access violation)가 발생합니다.
use-after-free 문제:
delete knight로 객체가 소멸됩니다. 하지만 화살(Arrow) 객체는 여전히 소멸된 기사를 가리키고 있어 _target->AddHp()나 _target->PrintInfo()를 호출하려고 하면 무효한 메모리 접근이 발생합니다.반복문 순서 문제:
knight 포인터의 값이 초기화되지 않은 상태로 남아있어 예외가 발생합니다.Arrow 클래스 분석:
Arrow::AttackTarget 메서드는 _target이 nullptr인지 확인하고 체력을 깎는 작업을 수행합니다._target이 유효하지 않은 경우, nullptr로 초기화하지 않으면 죽은 객체를 참조하게 됩니다.void Arrow::AttackTarget()
{
cout << "<화살이 적을 피격합니다!>" << endl;
// 공격 대상이 있다면
if (_target != nullptr)
{
// 데미지를 입힌다
_target->AddHp(-_damage);
_target->PrintInfo(); // 여기서 _target이 죽어있으면 오류 발생
}
}
main 함수 분석:
delete knight를 통해 소멸시킨 후에도 화살이 공격을 시도합니다.knight)을 가리키고 있는 화살은 여전히 타겟팅 작업을 수행하려고 하며, 이는 프로그램의 크래시로 이어집니다.for (int i = 0; i < 10; i++)
{
arrows[i]->AttackTarget();
// 기사가 죽었으면 소멸시켜준다
if (knight != nullptr)
{
if (knight->IsDead())
{
delete knight;
knight = nullptr; // knight 포인터 초기화
}
}
delete arrows[i];
arrows[i] = nullptr;
}
Arrow) 클래스의 AttackTarget 메서드에서 타겟이 유효한지 확인 후 작업을 수행합니다.nullptr로 초기화되지 않은 타겟에 접근하지 않도록 주의합니다.void Arrow::AttackTarget()
{
if (_target == nullptr)
{
cout << "타겟이 없습니다. 공격을 중지합니다." << endl;
return;
}
_target->AddHp(-_damage);
if (_target->IsDead())
{
cout << "타겟이 사망했습니다." << endl;
}
else
{
_target->PrintInfo();
}
}
knight 객체를 삭제하지 않고, 죽은 상태로 유지합니다.for (int i = 0; i < 10; i++)
{
arrows[i]->AttackTarget();
}
// 화살이 다 소멸된 후 실행
if (knight != nullptr)
{
delete knight;
knight = nullptr;
}
for (int i = 0; i < 10; i++)
{
// 기사 생존 여부를 먼저 확인
if (knight != nullptr && knight->IsDead())
{
delete knight;
knight = nullptr;
}
// 타겟이 유효할 때만 공격
if (knight != nullptr)
{
arrows[i]->AttackTarget();
}
delete arrows[i];
arrows[i] = nullptr;
}
// 화살 작업이 끝난 후 삭제
delete archer;
delete knight;