C/C++은 Java처럼 Garbage Collector가 자동으로 메모리를 관리해주는 언어와는 다르다. 개발자가 직접 메모리를 할당하고 해제해야 하기 때문에, 메모리 관리에 각별한 주의가 필요하다. 예를 들어 C에서는 malloc과 free를 사용해 메모리를 확보하고 반환하며, C++에서는 new와 delete가 그 역할을 맡는다. 이러한 특징으로 인해 프로그램의 효율성은 높일 수 있지만, 동시에 메모리 누수나 잘못된 해제와 같은 위험 역시 따라온다. 결과적으로, C/C++ 프로그래머는 항상 메모리 사용에 신경을 써야 하며, 이는 두 언어의 중요한 특징 중 하나라고 할 수 있다.
// 1. 메모리 누수
void memoryLeakExample() {
int* ptr = new int(42);
// delete 안 함 -> 메모리 누수
}
// 2. 널 포인터 이슈
void nullPointerExample() {
int* ptr = nullptr;
*ptr = 10; // 크래시!
}
// 3. 댕글링 포인터 이슈
void danglingPointerExample() {
int* ptr = new int(42);
delete ptr;
*ptr = 10; // 이미 해제된 메모리 접근
}
C++에서 동적 메모리를 안전하게 관리하기 위해 제공하는 템플릿 클래스
일반 포인터를 래핑하여 RAII(Resource Acquisition Is Initialization) 원칙에 따라 자동으로 메모리를 관리해준다.
Smart Pointer 객체가 생성될 때 메모리를 할당하고, 스코프를 벗어나 소멸자가 호출될 때 자동으로 메모리를 해제한다.
이를 통해 개발자가 명시적으로 delete를 호출하지 않아도 메모리 누수를 방지할 수 있다.
종류
Unique Ptr
move 연산만 가능하다.void uniquePtrExample() {
std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
// std::unique_ptr<int> ptr2 = ptr1; // 컴파일 에러
std::unique_ptr<int> ptr2 = std::move(ptr1); // 이동만 가능
}
Shared Ptr
Reference Counting을 통해 소유자 수를 추적한다.Reference Counting 관리로 인한 약간의 성능 오버헤드가 있다.Shared Ptr의 순환 참조는 메모리 누수를 발생시킬 수 있으므로, 이런 경우 Weak Ptr을 사용해야 한다.void sharedPtrExample() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1; // 복사 가능
std::cout << ptr1.use_count(); // 2
}
Weak Ptr
Shared Ptr과 함께 사용되며, 소유권 없이 객체를 참조만 하는 포인터Reference Counting을 증가시키지 않는다.Shared Ptr 간의 순환 참조 문제를 해결하는 데 사용된다.lock()은 객체가 아직 살아있으면 Shared Ptr을 반환하고, 이미 소멸되었으면 nullptr을 반환한다. void weakPtrExample() {
std::shared_ptr<int> shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared; // 참조 카운트 증가 안 함
if (auto ptr = weak.lock()) { // shared_ptr로 변환
std::cout << *ptr;
}
}