동적 힙 메모리 할당 (Dynamic Heap Memory Allocation)
개요
C++에서 메모리는 크게 스택(Stack)과 힙(Heap) 두 영역으로 나뉩니다. 동적 메모리 할당은 프로그램 실행 중에(런타임) 필요한 만큼의 메모리를 힙 영역에서 할당받아 사용하는 방식입니다. 컴파일 시점에 크기를 알 수 없거나, 객체의 생명주기를 특정 범위(scope)를 넘어서까지 제어하고 싶을 때 유용합니다.
반면, 함수 내에 선언된 일반 변수들은 스택에 저장되며, 해당 함수가 종료되면 자동으로 메모리에서 해제됩니다. 동적으로 할당된 힙 메모리는 프로그래머가 delete
키워드를 사용하여 명시적으로 해제하기 전까지 계속 유지되므로, 메모리 관리에 각별한 주의가 필요합니다.
C++ 버전별 주요 키워드 도입 시기
- C++98:
new
,delete
,new[]
,delete[]
연산자가 도입되어 C 스타일의malloc
/free
를 대체하는 표준적인 동적 메모리 관리 방법을 제공했습니다. - C++11: 메모리 누수와 같은 문제를 방지하기 위해 스마트 포인터(
std::unique_ptr
,std::shared_ptr
)와nullptr
가 도입되었습니다. 이는 RAII(Resource Acquisition Is Initialization) 패턴을 활용하여 메모리를 자동으로 관리합니다. - C++14:
std::make_unique
가 추가되어std::unique_ptr
를 더 안전하고 효율적으로 생성할 수 있게 되었습니다.
내용 설명
new
와 delete
new
연산자는 힙 영역에 특정 타입의 객체나 배열을 위한 메모리 공간을 할당하고, 해당 공간을 가리키는 포인터를 반환합니다.delete
연산자는new
를 통해 할당받았던 메모리를 해제합니다.- 단일 객체는
new
로 할당하고delete
로 해제하며, 배열은new T[size]
로 할당하고delete[]
로 해제해야 합니다. 이 둘을 혼용하면 미정의 동작(Undefined Behavior)이 발생할 수 있습니다.
메모리 누수 (Memory Leak)
new
로 할당된 메모리를 delete
로 해제하지 않으면, 해당 메모리는 프로그램이 종료될 때까지 계속 힙 공간을 차지하게 됩니다. 이러한 현상을 메모리 누수라고 하며, 프로그램의 성능 저하 및 비정상 종료의 원인이 될 수 있습니다.
댕글링 포인터 (Dangling Pointer)
delete
로 메모리를 해제한 후에도 포인터 변수 자체에는 여전히 이전의 메모리 주소가 남아있습니다. 이 포인터를 댕글링 포인터라고 하며, 이를 통해 이미 해제된 메모리에 접근하려고 시도하면 심각한 오류가 발생할 수 있습니다. 이를 방지하기 위해 메모리 해제 후 포인터를 nullptr
로 초기화하는 것이 좋은 습관입니다.
예제 코드
#include <iostream>
#include <cstdint>
#include <vector> // 스마트 포인터를 사용하지 않을 경우 대안
int main()
{
// 정적 배열 (스택 할당)
const auto len = std::size_t{5};
std::uint32_t my_array[len] = {1, 2, 3, 4, 5};
// 동적 배열 크기 입력 받기
auto len2 = std::size_t{};
std::cout << "배열 크기를 입력하세요: ";
std::cin >> len2;
// 동적 메모리 할당 (힙 할당)
std::uint32_t *heap_arr = new(std::nothrow) std::uint32_t[len2]; // std::nothrow는 할당 실패 시 예외 대신 nullptr 반환
if (heap_arr != nullptr)
{
std::cout << "할당된 메모리 주소: " << heap_arr << '\n';
// 배열 초기화
for (std::size_t i = 0; i < len2; i++)
{
heap_arr[i] = static_cast<std::uint32_t>(i);
}
// 배열 출력
std::cout << "배열 내용:\n";
for (std::size_t i = 0; i < len2; i++)
{
std::cout << heap_arr[i] << '\n';
}
// 메모리 해제
delete[] heap_arr;
heap_arr = nullptr; // 댕글링 포인터 방지
}
else
{
std::cout << "메모리 할당에 실패했습니다." << std::endl;
}
// 포인터 유효성 검사
if (!heap_arr)
{
std::cout << "유효하지 않은 포인터입니다!\n";
}
return 0;
}
실행 결과
배열 크기를 입력하세요: 5
할당된 메모리 주소: 0x1f8d8efb650
배열 내용:
0
1
2
3
4
유효하지 않은 포인터입니다!
활용팁
- 최신 C++에서는 직접적인
new
/delete
사용을 지양합니다. 메모리 누수, 댕글링 포인터와 같은 실수를 방지하기 위해 스마트 포인터(std::unique_ptr
,std::shared_ptr
) 사용이 강력히 권장됩니다. - 동적 크기의 배열이 필요할 때는
new T[]
대신std::vector
를 사용하는 것이 훨씬 안전하고 편리합니다.std::vector
는 내부적으로 동적 메모리를 사용하지만, 소멸자에서 자동으로 메모리를 해제해주므로 개발자가 직접 관리할 필요가 없습니다. - 메모리 할당이 실패할 경우
new
는std::bad_alloc
예외를 발생시킵니다. 예외 처리를 원하지 않는다면new(std::nothrow)
를 사용하여 할당 실패 시nullptr
를 반환받도록 할 수 있습니다.
'개발 > C++ (98,03,11,14,17,20,23)' 카테고리의 다른 글
Modern C++ : pointer and address (98, 11) (2) | 2025.08.13 |
---|---|
Modern C++ : const reference and value semantics (98, 11) (1) | 2025.08.12 |
Modern C++ : string vs std::string vs std::array<char, N> (98, 11, 17) (1) | 2025.08.11 |
Modern C++ : array vs std::array (98, 11) (1) | 2025.08.10 |
Modern C++ : anonymous namespace (98, 11, 17) (2) | 2025.08.09 |
Modern C++ : C-Style static function (98, 11) (1) | 2025.08.08 |
Modern C++ : static local variables (98, 11) (3) | 2025.08.07 |
Modern C++ : const vs constexpr (98, 11, 14, 17, 20) (0) | 2025.08.06 |