Topic 4: 메모리 관리와 최적화 ⚡
🎯 학습 목표
- 메모리 누수의 원인과 방지 기법을 이해할 수 있다
- RAII 원칙을 활용한 안전한 리소스 관리를 구현할 수 있다
- 스마트 포인터를 활용하여 자동 메모리 관리를 할 수 있다
- 성능 프로파일링 도구로 병목 지점을 찾을 수 있다
- 게임 최적화 기법을 실제 프로젝트에 적용할 수 있다
💧 메모리는 물탱크
메모리 관리는 물탱크 관리와 비슷해요! 🏠
물탱크 시스템과 메모리 관리의 유사점:
-
물 공급 = 메모리 할당 🚰
- 필요할 때 물(메모리)을 공급
- 적절한 양만큼 사용
-
물 사용 = 프로그램 실행 💧
- 할당된 메모리로 작업 수행
- 효율적으로 사용해야 함
-
배수구 = 메모리 해제 🕳️
- 사용 완료된 메모리 반환
- 막히면(누수) 문제 발생
-
물 넘침 = 메모리 부족 🌊
- 할당만 하고 해제 안 함
- 시스템 전체에 영향
// 물탱크처럼 메모리도 순환되어야 함
void* waterTank = malloc(1000); // 🚰 물 공급 (할당)
// ... 물 사용 (메모리 사용) ...
free(waterTank); // 🕳️ 배수 (해제)
waterTank = nullptr; // 🔒 밸브 잠금 (안전)🚨 메모리 누수의 종류와 해결책
1. 기본적인 메모리 누수
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class LeakyGameObject {
public:
string name;
int* data;
// ❌ 잘못된 방법 - 메모리 누수 발생
LeakyGameObject(const string& n) : name(n) {
data = new int[1000]; // 할당은 했지만...
cout << "🎮 " << name << " 생성 (메모리 할당)" << endl;
}
// 소멸자가 없어서 메모리 해제 안됨! 💥
~LeakyGameObject() {
cout << "💀 " << name << " 소멸 (메모리 누수 발생!)" << endl;
// delete[] data; 를 빼먹었음!
}
};
class SafeGameObject {
public:
string name;
int* data;
// ✅ 올바른 방법 - RAII 원칙 준수
SafeGameObject(const string& n) : name(n) {
data = new int[1000];
cout << "🛡️ " << name << " 안전 생성" << endl;
}
// 소멸자에서 반드시 메모리 해제
~SafeGameObject() {
delete[] data;
data = nullptr;
cout << "✅ " << name << " 안전 소멸" << endl;
}
// 복사 생성자와 대입 연산자도 정의 (Rule of Three)
SafeGameObject(const SafeGameObject& other) : name(other.name + "_copy") {
data = new int[1000];
// 데이터 복사
for (int i = 0; i < 1000; i++) {
data[i] = other.data[i];
}
cout << "📄 " << name << " 복사 생성" << endl;
}
SafeGameObject& operator=(const SafeGameObject& other) {
if (this != &other) {
delete[] data; // 기존 메모리 해제
name = other.name + "_assigned";
data = new int[1000];
// 데이터 복사
for (int i = 0; i < 1000; i++) {
data[i] = other.data[i];
}
cout << "📝 " << name << " 대입" << endl;
}
return *this;
}
};
void demonstrateMemoryLeaks() {
cout << "🚨 메모리 누수 시연" << endl;
cout << "==================" << endl;
{
cout << "📦 Leaky 객체들 생성:" << endl;
LeakyGameObject enemy1("고블린1");
LeakyGameObject enemy2("고블린2");
cout << "\n🛡️ Safe 객체들 생성:" << endl;
SafeGameObject hero1("전사1");
SafeGameObject hero2("전사2");
cout << "\n📄 객체 복사 테스트:" << endl;
SafeGameObject hero3 = hero1; // 복사 생성자
hero2 = hero1; // 대입 연산자
} // 여기서 모든 객체의 소멸자가 호출됨
cout << "\n💡 결과: Safe 객체들만 메모리를 올바르게 해제했습니다!" << endl;
}2. 스마트 포인터를 활용한 자동 메모리 관리
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class GameAsset {
public:
string name;
vector<int> data;
GameAsset(const string& n) : name(n), data(1000, 42) {
cout << "🎨 에셋 '" << name << "' 로딩 완료" << endl;
}
~GameAsset() {
cout << "🗑️ 에셋 '" << name << "' 메모리 해제" << endl;
}
void use() {
cout << "🖼️ " << name << " 에셋 사용 중..." << endl;
}
};
class SmartPointerDemo {
public:
void demonstrateUniquePtr() {
cout << "🔹 unique_ptr 데모:" << endl;
cout << "==================" << endl;
// unique_ptr - 독점 소유권
unique_ptr<GameAsset> texture = make_unique<GameAsset>("메인텍스처");
texture->use();
// 다른 unique_ptr로 소유권 이동
unique_ptr<GameAsset> movedTexture = move(texture);
if (!texture) {
cout << "🔄 원본 포인터는 nullptr이 됨" << endl;
}
movedTexture->use();
// 함수 종료 시 자동으로 메모리 해제됨
cout << "📤 함수 종료 - 자동 해제 예정...\n" << endl;
}
void demonstrateSharedPtr() {
cout << "🔸 shared_ptr 데모:" << endl;
cout << "==================" << endl;
// shared_ptr - 공유 소유권
shared_ptr<GameAsset> sharedAsset = make_shared<GameAsset>("공유음악");
cout << "📊 참조 카운트: " << sharedAsset.use_count() << endl;
{
shared_ptr<GameAsset> copy1 = sharedAsset;
cout << "📊 참조 카운트: " << sharedAsset.use_count() << endl;
{
shared_ptr<GameAsset> copy2 = sharedAsset;
cout << "📊 참조 카운트: " << sharedAsset.use_count() << endl;
copy2->use();
} // copy2 소멸
cout << "📊 참조 카운트: " << sharedAsset.use_count() << endl;
} // copy1 소멸
cout << "📊 참조 카운트: " << sharedAsset.use_count() << endl;
// 마지막 shared_ptr이 소멸될 때 자동으로 메모리 해제
cout << "📤 함수 종료 - 자동 해제 예정...\n" << endl;
}
void demonstrateWeakPtr() {
cout << "🔺 weak_ptr 데모 (순환 참조 해결):" << endl;
cout << "==================================" << endl;
// 순환 참조 문제를 해결하는 weak_ptr
shared_ptr<GameAsset> parent = make_shared<GameAsset>("부모객체");
weak_ptr<GameAsset> child = parent; // weak 참조
cout << "📊 strong 참조 카운트: " << parent.use_count() << endl;
if (auto locked = child.lock()) { // weak_ptr을 shared_ptr로 변환
cout << "🔓 child가 여전히 유효함" << endl;
locked->use();
}
parent.reset(); // parent를 nullptr로 만듦
if (child.expired()) {
cout << "⚰️ child가 참조하는 객체가 소멸됨" << endl;
}
cout << endl;
}
};
void smartPointerComparison() {
cout << "🧠 스마트 포인터 비교 테스트" << endl;
cout << "==============================" << endl;
SmartPointerDemo demo;
demo.demonstrateUniquePtr();
demo.demonstrateSharedPtr();
demo.demonstrateWeakPtr();
}🎮 게임 리소스 풀링 시스템
메모리 효율적인 객체 풀
#include <iostream>
#include <vector>
#include <memory>
#include <queue>
#include <chrono>
using namespace std;
using namespace chrono;
class Bullet {
private:
float x, y;
float vx, vy;
bool active;
static int totalCreated;
static int currentActive;
public:
Bullet() : x(0), y(0), vx(0), vy(0), active(false) {
totalCreated++;
cout << "🎯 총알 객체 생성 #" << totalCreated << endl;
}
~Bullet() {
cout << "💥 총알 객체 소멸" << endl;
}
void activate(float startX, float startY, float velX, float velY) {
x = startX;
y = startY;
vx = velX;
vy = velY;
active = true;
currentActive++;
cout << "🚀 총알 발사: (" << x << ", " << y << ") 방향(" << vx << ", " << vy << ")" << endl;
}
void update(float deltaTime) {
if (!active) return;
x += vx * deltaTime;
y += vy * deltaTime;
// 화면 밖으로 나가면 비활성화
if (x < 0 || x > 800 || y < 0 || y > 600) {
deactivate();
}
}
void deactivate() {
if (active) {
active = false;
currentActive--;
cout << "💀 총알 비활성화: (" << x << ", " << y << ")" << endl;
}
}
bool isActive() const { return active; }
static void printStats() {
cout << "📊 총알 통계 - 생성됨: " << totalCreated
<< ", 활성: " << currentActive << endl;
}
};
int Bullet::totalCreated = 0;
int Bullet::currentActive = 0;
class BulletPool {
private:
vector<unique_ptr<Bullet>> bullets;
queue<Bullet*> available;
int poolSize;
public:
BulletPool(int size = 100) : poolSize(size) {
cout << "🏊 총알 풀 생성 (크기: " << size << ")" << endl;
// 미리 총알 객체들을 생성해두기
for (int i = 0; i < poolSize; i++) {
auto bullet = make_unique<Bullet>();
available.push(bullet.get());
bullets.push_back(move(bullet));
}
cout << "✅ 총알 풀 초기화 완료!" << endl;
}
Bullet* getBullet(float x, float y, float vx, float vy) {
if (available.empty()) {
cout << "⚠️ 총알 풀이 비어있음! nullptr 반환" << endl;
return nullptr;
}
Bullet* bullet = available.front();
available.pop();
bullet->activate(x, y, vx, vy);
return bullet;
}
void returnBullet(Bullet* bullet) {
if (bullet && bullet->isActive()) {
bullet->deactivate();
available.push(bullet);
}
}
void updateAll(float deltaTime) {
for (auto& bullet : bullets) {
if (bullet->isActive()) {
bullet->update(deltaTime);
// 비활성화된 총알을 풀로 반환
if (!bullet->isActive()) {
available.push(bullet.get());
}
}
}
}
void printPoolStatus() {
cout << "🏊 풀 상태 - 사용 가능: " << available.size()
<< "/" << poolSize << endl;
}
};
void demonstrateBulletPool() {
cout << "🎮 게임 총알 풀링 시스템 데모" << endl;
cout << "===============================" << endl;
BulletPool pool(10); // 10개 총알 풀
vector<Bullet*> activeBullets;
// 총알 발사 시뮬레이션
for (int i = 0; i < 15; i++) { // 풀 크기보다 많이 요청
float x = 100 + i * 10;
float y = 300;
float vx = 200;
float vy = -100 + i * 20;
Bullet* bullet = pool.getBullet(x, y, vx, vy);
if (bullet) {
activeBullets.push_back(bullet);
}
pool.printPoolStatus();
}
cout << "\n🔄 총알 업데이트 시뮬레이션:" << endl;
// 게임 루프 시뮬레이션
for (int frame = 0; frame < 5; frame++) {
cout << "\n📺 프레임 " << (frame + 1) << ":" << endl;
float deltaTime = 0.1f;
pool.updateAll(deltaTime);
pool.printPoolStatus();
Bullet::printStats();
}
cout << "\n✅ 시뮬레이션 완료 - 모든 총알이 자동으로 관리됨!" << endl;
}⚡ 성능 프로파일링과 최적화
성능 측정 도구
#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
#include <random>
#include <map>
using namespace std;
using namespace chrono;
class PerformanceProfiler {
private:
map<string, duration<double>> timings;
map<string, int> callCounts;
public:
class Timer {
private:
string name;
high_resolution_clock::time_point startTime;
PerformanceProfiler* profiler;
public:
Timer(const string& timerName, PerformanceProfiler* prof)
: name(timerName), profiler(prof) {
startTime = high_resolution_clock::now();
}
~Timer() {
auto endTime = high_resolution_clock::now();
auto elapsed = endTime - startTime;
profiler->timings[name] += elapsed;
profiler->callCounts[name]++;
}
};
void printReport() {
cout << "\n📊 성능 프로파일링 리포트" << endl;
cout << "=========================" << endl;
for (const auto& pair : timings) {
const string& name = pair.first;
double totalMs = duration_cast<milliseconds>(pair.second).count();
int calls = callCounts[name];
double avgMs = totalMs / calls;
cout << "🔹 " << name << endl;
cout << " 총 시간: " << totalMs << "ms" << endl;
cout << " 호출 횟수: " << calls << "회" << endl;
cout << " 평균 시간: " << avgMs << "ms" << endl;
cout << endl;
}
}
void reset() {
timings.clear();
callCounts.clear();
}
};
// 편의용 매크로
#define PROFILE_FUNCTION(profiler) PerformanceProfiler::Timer _timer(__FUNCTION__, profiler)
#define PROFILE_SCOPE(profiler, name) PerformanceProfiler::Timer _timer(name, profiler)
class GameOptimizationDemo {
private:
PerformanceProfiler profiler;
vector<int> gameData;
public:
GameOptimizationDemo() {
// 큰 데이터셋 생성
gameData.resize(100000);
random_device rd;
mt19937 gen(rd());
uniform_int_distribution<> dis(1, 1000);
for (int& val : gameData) {
val = dis(gen);
}
cout << "🎮 게임 데이터 준비 완료 (" << gameData.size() << "개 요소)" << endl;
}
// 비효율적인 정렬 (버블 정렬)
void inefficientSort() {
PROFILE_FUNCTION(&profiler);
vector<int> data = gameData; // 복사
for (size_t i = 0; i < data.size() - 1; i++) {
for (size_t j = 0; j < data.size() - i - 1; j++) {
if (data[j] > data[j + 1]) {
swap(data[j], data[j + 1]);
}
}
}
cout << "🐌 비효율적 정렬 완료" << endl;
}
// 효율적인 정렬 (STL sort)
void efficientSort() {
PROFILE_FUNCTION(&profiler);
vector<int> data = gameData; // 복사
sort(data.begin(), data.end());
cout << "🚀 효율적 정렬 완료" << endl;
}
// 비효율적인 검색 (선형 검색)
void inefficientSearch(int target) {
PROFILE_FUNCTION(&profiler);
int found = 0;
for (int val : gameData) {
if (val == target) {
found++;
}
}
cout << "🔍 선형 검색 결과: " << found << "개 발견" << endl;
}
// 효율적인 검색 (정렬 후 이진 검색)
void efficientSearch(int target) {
PROFILE_SCOPE(&profiler, "efficientSearch");
{
PROFILE_SCOPE(&profiler, "efficientSearch_sort");
vector<int> sortedData = gameData;
sort(sortedData.begin(), sortedData.end());
}
{
PROFILE_SCOPE(&profiler, "efficientSearch_binarySearch");
vector<int> sortedData = gameData;
sort(sortedData.begin(), sortedData.end());
auto range = equal_range(sortedData.begin(), sortedData.end(), target);
int found = distance(range.first, range.second);
cout << "🎯 이진 검색 결과: " << found << "개 발견" << endl;
}
}
// 메모리 집약적 연산
void memoryIntensiveOperation() {
PROFILE_FUNCTION(&profiler);
vector<vector<int>> matrix(1000, vector<int>(1000, 1));
// 행렬 연산 시뮬레이션
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 1000; j++) {
matrix[i][j] = i * j;
}
}
cout << "💾 메모리 집약적 연산 완료" << endl;
}
// 캐시 친화적 연산
void cacheOptimizedOperation() {
PROFILE_FUNCTION(&profiler);
vector<int> linearData(1000000, 1);
// 순차 접근 (캐시 친화적)
for (size_t i = 0; i < linearData.size(); i++) {
linearData[i] = i % 100;
}
cout << "🏎️ 캐시 최적화 연산 완료" << endl;
}
void runBenchmarks() {
cout << "\n🏁 성능 벤치마크 시작!" << endl;
cout << "========================" << endl;
// 정렬 성능 비교
cout << "📈 정렬 성능 테스트:" << endl;
// 비효율적 정렬은 데이터가 너무 크면 시간이 오래 걸리므로 주석 처리
// inefficientSort();
efficientSort();
// 검색 성능 비교
cout << "\n🔍 검색 성능 테스트:" << endl;
int target = 500;
inefficientSearch(target);
efficientSearch(target);
// 메모리 연산 성능 비교
cout << "\n💾 메모리 연산 테스트:" << endl;
memoryIntensiveOperation();
cacheOptimizedOperation();
// 결과 리포트
profiler.printReport();
}
};
class MemoryLeakDetector {
private:
static size_t totalAllocated;
static size_t currentAllocated;
static bool trackingEnabled;
public:
static void enableTracking() {
trackingEnabled = true;
totalAllocated = 0;
currentAllocated = 0;
cout << "🔍 메모리 추적 시작" << endl;
}
static void disableTracking() {
trackingEnabled = false;
cout << "🛑 메모리 추적 중단" << endl;
}
static void* trackedMalloc(size_t size) {
void* ptr = malloc(size);
if (trackingEnabled && ptr) {
totalAllocated += size;
currentAllocated += size;
cout << "📈 메모리 할당: " << size << " bytes (총: "
<< currentAllocated << " bytes)" << endl;
}
return ptr;
}
static void trackedFree(void* ptr, size_t size) {
if (trackingEnabled && ptr) {
currentAllocated -= size;
cout << "📉 메모리 해제: " << size << " bytes (총: "
<< currentAllocated << " bytes)" << endl;
}
free(ptr);
}
static void printMemoryReport() {
cout << "\n📊 메모리 사용 리포트" << endl;
cout << "=====================" << endl;
cout << "총 할당량: " << totalAllocated << " bytes" << endl;
cout << "현재 사용량: " << currentAllocated << " bytes" << endl;
if (currentAllocated == 0) {
cout << "✅ 메모리 누수 없음!" << endl;
} else {
cout << "⚠️ 메모리 누수 감지: " << currentAllocated << " bytes" << endl;
}
}
};
size_t MemoryLeakDetector::totalAllocated = 0;
size_t MemoryLeakDetector::currentAllocated = 0;
bool MemoryLeakDetector::trackingEnabled = false;
void demonstrateMemoryTracking() {
cout << "🕵️ 메모리 누수 감지 데모" << endl;
cout << "==========================" << endl;
MemoryLeakDetector::enableTracking();
// 메모리 할당 테스트
void* ptr1 = MemoryLeakDetector::trackedMalloc(1000);
void* ptr2 = MemoryLeakDetector::trackedMalloc(2000);
void* ptr3 = MemoryLeakDetector::trackedMalloc(1500);
// 일부 메모리만 해제 (누수 시뮬레이션)
MemoryLeakDetector::trackedFree(ptr1, 1000);
MemoryLeakDetector::trackedFree(ptr2, 2000);
// ptr3는 해제하지 않음! (의도적 누수)
MemoryLeakDetector::printMemoryReport();
// 나머지 메모리 해제
cout << "\n🔧 누수 메모리 정리..." << endl;
MemoryLeakDetector::trackedFree(ptr3, 1500);
MemoryLeakDetector::printMemoryReport();
MemoryLeakDetector::disableTracking();
}🎯 게임 최적화 실전 기법
최적화 체크리스트
class GameOptimizationTips {
public:
void demonstrateOptimizations() {
cout << "🎮 게임 최적화 실전 기법" << endl;
cout << "========================" << endl;
// 1. 객체 풀링
cout << "1️⃣ 객체 풀링:" << endl;
cout << " ✅ 자주 생성/소멸되는 객체는 미리 만들어두기" << endl;
cout << " ✅ 총알, 파티클, 임시 객체들에 적용" << endl;
cout << endl;
// 2. 메모리 지역성 활용
cout << "2️⃣ 메모리 지역성:" << endl;
cout << " ✅ 관련된 데이터를 연속된 메모리에 배치" << endl;
cout << " ✅ 구조체 멤버 순서 최적화" << endl;
cout << endl;
// 3. 불필요한 복사 방지
cout << "3️⃣ 불필요한 복사 방지:" << endl;
cout << " ✅ const 참조 매개변수 사용" << endl;
cout << " ✅ move 시맨틱 활용" << endl;
cout << " ✅ 반환값 최적화 (RVO)" << endl;
cout << endl;
// 4. 조건부 컴파일
cout << "4️⃣ 조건부 컴파일:" << endl;
cout << " ✅ DEBUG/RELEASE 모드 구분" << endl;
cout << " ✅ 프로파일링 코드 제거" << endl;
cout << endl;
// 5. 알고리즘 최적화
cout << "5️⃣ 알고리즘 최적화:" << endl;
cout << " ✅ 시간 복잡도 개선 (O(n²) → O(n log n))" << endl;
cout << " ✅ 적절한 자료구조 선택" << endl;
cout << endl;
}
// 최적화 전후 비교 예제
void showOptimizationExample() {
cout << "📈 최적화 전후 비교 예제" << endl;
cout << "=========================" << endl;
// ❌ 최적화 전 (비효율적)
cout << "❌ 최적화 전:" << endl;
showInefficient();
cout << endl;
// ✅ 최적화 후 (효율적)
cout << "✅ 최적화 후:" << endl;
showEfficient();
}
private:
void showInefficient() {
cout << " - 매번 vector 생성하고 복사" << endl;
cout << " - 불필요한 동적 할당" << endl;
cout << " - 비효율적인 알고리즘 사용" << endl;
}
void showEfficient() {
cout << " - 미리 할당된 컨테이너 재사용" << endl;
cout << " - 스택 메모리 활용" << endl;
cout << " - 최적화된 STL 알고리즘 사용" << endl;
}
};
int main() {
cout << "⚡ C++ 메모리 관리와 최적화 마스터 클래스 ⚡" << endl;
cout << "=============================================" << endl;
// 1. 메모리 누수 시연
demonstrateMemoryLeaks();
cout << endl;
// 2. 스마트 포인터 비교
smartPointerComparison();
// 3. 객체 풀링 시스템
demonstrateBulletPool();
cout << endl;
// 4. 성능 프로파일링
GameOptimizationDemo optimizer;
optimizer.runBenchmarks();
// 5. 메모리 추적
demonstrateMemoryTracking();
cout << endl;
// 6. 최적화 팁
GameOptimizationTips tips;
tips.demonstrateOptimizations();
tips.showOptimizationExample();
cout << "\n🎉 축하합니다! C++ 예외 처리와 디버깅 마스터!" << endl;
cout << "================================================" << endl;
cout << "🏆 당신은 이제 안전하고 효율적인 C++ 프로그램을 만들 수 있습니다!" << endl;
cout << "🛡️ 예외 처리로 견고한 프로그램" << endl;
cout << "🔍 디버깅으로 완벽한 코드" << endl;
cout << "⚡ 최적화로 고성능 구현" << endl;
cout << "💎 메모리 관리로 안정적인 시스템" << endl;
return 0;
}💡 핵심 정리
- RAII 원칙: 리소스 획득과 해제를 생성자/소멸자에서 자동 처리
- 스마트 포인터:
unique_ptr,shared_ptr,weak_ptr로 안전한 메모리 관리 - 객체 풀링: 자주 생성/소멸되는 객체를 미리 할당하여 성능 최적화
- 성능 프로파일링: 실제 측정을 통한 병목 지점 식별과 최적화
- 메모리 추적: 할당/해제 패턴 분석으로 누수 방지
- 최적화 기법: 알고리즘 개선, 메모리 지역성, 불필요한 복사 방지
✅ 실습 체크리스트
🎉 Unit 15 완주를 축하합니다!
🏆 여러분은 이제 C++ 예외 처리와 디버깅의 전문가가 되었습니다!
배운 내용 요약:
- 🛡️ 예외 처리: try-catch-throw로 안전한 프로그램 작성
- 🎨 사용자 정의 예외: 프로젝트별 맞춤형 예외 시스템
- 🔍 디버깅 기법: 체계적인 문제 해결과 로깅 시스템
- ⚡ 메모리 최적화: 스마트 포인터와 성능 최적화 기법
다음 단계:
- 🚀 실전 프로젝트: 배운 모든 기술을 종합한 큰 프로젝트 도전
- 🌐 고급 주제: 멀티스레딩, 네트워크 프로그래밍 탐구
- 🎮 게임 엔진: 나만의 게임 엔진 개발 시작
“완벽한 C++ 개발자로 성장하는 여정을 계속하세요! 💎✨”
Last updated on