Outdated/Core Language

[C++ Core] 상수 구문 - new 연산자

해달 2018. 8. 29. 12:30

[C++ Core] 상수 구문 - new 연산자

자유 영역

변수들은 모두 각자만의 유효범위(scope)와 수명주기(lifetime)가 존재한다. 지역 변수인 경우에는 선언된 유효범위를 벗어나게 되면 자동으로 없어진다. 하지만, 간혹 유효범위와 독립적으로 존재하는 변수들이 유용할 때가 있다. 가령 함수 안에 있는 객체를 반환하는 경우말이다. 그런 변수들은 스택 대신 자유 영역(free store)에 존재하며 이 영역은 (heap), 동적 메모리(dynamic memory)라고도 불린다. 자유 영역에 존재하는 변수를 만드려면 new와 delete 연산자를 사용하면 된다.

// C11 : 중괄호 초기자를 통해 초기화를 할 수 있다.
int* ip = new int{ 2 };
// new[]를 이용하면 연속된 메모리 영역을 할당할 수 있다.
int* ip2 = new int[5]{ 1, 2, 3, 4, 5 };

// 메모리는 자원이기 때문에 사용이 끝나면 반환해줘야 한다.
// new와 delete, new[]와 delete[] 쌍은 꼭 맞춰야 한다.
delete ip;
delete[] ip2;

// 메모리를 반환해주고 나면 포인터는 유효하지 않은 곳을 가리키게 된다.
// 이를 댕글링 포인터(dangling pointer)라 한다.
// 이중 삭제를 방지하기 위해 nullptr로 할당하는 경우도 있다.
ip = ip2 = nullptr; 

// nullptr에 delete를 해도 아무런 효과가 일어나지 않는다.
delete ip;

자유 영역 연산자는 모두 <new> 헤더에 존재하며, 연산자 오버로딩이 가능하다. new 연산자의 시그너처가 궁금하다면 여기를 방문해보자. delete 연산자는 여기에서 볼 수 있다. 생각보다 많은 수에 놀랄지도 모르겠으나 크게 보면 (1)일반적으로 사용하는 new (2)nothrow new 그리고 (3)위치지정 new로 나눌 수 있다. 1번은 많이 사용하는 것이고, 자주 접한 것이니 2번과 3번을 자세히 살펴보자.

nothrow new

new 연산자는 메모리 할당 시 메모리가 부족하면 std::bad_alloc 예외를 내뱉는다. 예외 처리는 성능을 꽤 잡아먹기에, 성능이 중요한 프로그램에서는 달갑진 않을 것이다. 이럴 때 nothrow new를 사용할 수 있다. nothrow new는 이름처럼 예외 대신 nullptr을 반환한다.

SomeHeavyType* p = new(std::nothrow) SomeHeavyType;
if (p)
{
    // do something
}

위치지정 new

new를 이용하면 간편하게 자유 영역에 존재하는 변수를 만들 수 있다. 그러나, 메모리 파편화가 발생할 수 있고, 자주 메모리 할당이 일어나는 프로그램이라면 메모리 사용량을 분석하여 한번에 할당을 해놓고 스택처럼 사용하는 것이 성능 향상에 도움을 줄 것이다. 이를 위해 위치지정 new를 사용할 수 있다.

using byte = unsigned char;
class A;

// allocate
byte* memory = new byte[10000];
A* pA = new(memory) A;

// do something

// delete
// 위치지정 delete도 물론 있지만 아무것도 수행하지 않는다.
// ex: delete(pA, memory);
pA->~A(); // 소멸자를 호출하는 것이 중요하다.
delete[] memory;

이를 기반으로 한 메모리풀(memory pool)이라는 자료구조도 존재한다. 메모리풀은 다음에 기회가 되면 소개하도록 하겠다.