1장: C 스타일 메모리 관리에서 현대 C++로의 전환
C++은 시스템 프로그래밍의 안정성과 효율성, 추상화 능력을 지속적으로 개선하는 과정에서 발전해 왔습니다. 초기 C++은 C 언어의 malloc/free를 기반으로 동적 메모리 할당을 수행했으나, 이는 메모리 누수, 중복 해제, 떠 있는 포인터 등 다양한 문제를 유발했습니다.전통적인 C 스타일 메모리 관리의 문제점
- 개발자가 메모리 생명주기를 명시적으로 관리해야 함 - 생성자/소멸자 지원 없음으로 자동 초기화/정리 불가 - 잘못된 할당/해제(예: malloc 후 delete 사용)로 undefined behavior 발생현대 C++의 리소스 관리 철학
문제 해결을 위해 C++은 객체 지향과 RAII(리소스 획득은 초기화) 기법을 도입했습니다. 핵심은 리소스의 생명주기를 객체의 생성/소멸과 연결하는 것입니다.#include <iostream>
#include <memory>
int main() {
// 스마트 포인터로 자동 메모리 관리
std::unique_ptr<int> ptr = std::make_unique<int>(42);
std::cout << "Value: " << *ptr << std::endl;
// 범위 탈출 시 자동 해제, 수동 delete 필요 없음
return 0;
}
이 코드는 std::unique_ptr을 통해 수동 메모리 관리의 위험성을 제거합니다. 소멸자 호출 시 자동 delete를 수행하여 리소스 정확히 해제합니다.
핵심 진화 비교표
| 특성 | C 스타일 | 현대 C++ |
|---|---|---|
| 메모리 할당 | malloc/free | new/delete + 스마트 포인터 |
| 예외 안정성 | 낮음 | 높음 (RAII 보장) |
| 자동 리소스 관리 | 무 | 있음 (소멸자 의존) |
2장: C 언어 시대의 동적 메모리 할당
2.1 malloc과 calloc의 작동 원리 및 차이 분석
핵심 차이 비교
- malloc(size_t size): 단순 할당, 초기화 무 - calloc(size_t count, size_t size): 할당 및 0 초기화int *p1 = malloc(5 * sizeof(int)); // 할당만
int *p2 = calloc(5, sizeof(int)); // 할당 및 0 초기화
p1은 임의 값이 포함될 수 있고, p2는 배열이나 구조체 초기화에 적합합니다.
성능 및 사용 권장 사항
calloc의 추가 초기화 단계로 인해 성능은 malloc보다 약간 낮습니다. 고성능이 필요한 경우 malloc을, 0 초기화가 필요한 경우 calloc을 사용하는 것이 좋습니다.3장: RAII와 스마트 포인터의 부상
3.1 RAII 원리와 리소스 관리 혁신
RAII(리소스 획득은 초기화)는 C++에서 객체 생명주기로 리소스를 관리하는 핵심 기술입니다. 핵심 아이디어는 리소스의 획득과 객체 생성이 동시에 일어나고, 해제는 소멸자 자동 수행입니다.class FileHandler {
FILE* file;
public:
explicit FileHandler(const char* path) {
file = fopen(path, "r");
if (!file) throw std::runtime_error("파일 열기 실패");
}
~FileHandler() {
if (file) fclose(file);
}
FILE* get() const { return file; }
};
이 예제에서 파일 포인터는 생성 시 획득하고, 소멸 시 자동 닫습니다. 함수 조기 반환 또는 예외 발생 시에도 스택 확장 메커니즘으로 소멸자 호출이 보장됩니다.
3.2 unique_ptr 설계 철학과 이동 세마포 사용
unique_ptr은 "독점 소유권"을 원칙으로 하며, 복사 생성자/할당자 비활성화로 같은 리소스의 다중 참조 방지합니다. 이동 세마포는 소유권 이전을 가능하게 합니다.std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int> ptr2 = std::move(ptr1); // 소유권 이전
// ptr1은 null, ptr2는 원래 메모리 참조
이 메커니즘은 깊은 복사 오버헤드 없으면서 메모리 안정성 유지합니다.
4장: 현대 C++ 메모리 관리 최적화 실천
4.1 적절한 스마트 포인터 선택: 상황별 비교 분석
std::unique_ptr, std::shared_ptr, std::weak_ptr 각각의 적절한 사용 장면:
| 포인터 유형 | 소유권 | 적용 장면 |
|---|---|---|
| unique_ptr | 독점 | 단일 소유자 |
| shared_ptr | 공유 | 다중 소유자 |
| weak_ptr | 관찰 | 순환 참조 방지 |
4.2 커스텀 삭제자 및 할당자 고급 활용
메모리 매핑 자원 해제를 위한 커스텀 삭제자 예시:
struct MappedDeleter {
void operator()(int* ptr) const {
if (ptr) munmap(ptr, 4096);
}
};
std::unique_ptr mapped_mem{static_cast(mmap(...))};
이 삭제자는 mmap 내부 자원 해제를 보장하며 누수 방지합니다.