다음과 같은 코드가 있다. 현 시점에서 add_exception
및 add_assert
함수의 로직에 대해서는 고려하지 않는다.
int main() {
int size = 1000000000;
// x, y의 입력값은 각각 정상범위 (-2,147,483,648 ~ 2,147,483,647)라고 가정.
int32_t x = 20000;
int32_t y = 10000;
int32_t *result_exception = new int32_t[size];
int32_t *result_assert = new int32_t[size];
std::chrono::time_point<std::chrono::system_clock> start, end;
std::chrono::duration<double> elapsed;
start = std::chrono::system_clock::now();
for(int i=0; i<size; ++i) {
try {
result_exception[i] = add2_exception(x, y);
} catch(invalid_argument& ia) {
cout << ia.what() << endl;
}
}
end = std::chrono::system_clock::now();
elapsed = end - start;
std::cout << "using exception: " << elapsed.count() << " seconds" << std::endl;
std::chrono::time_point<std::chrono::system_clock> start2, end2;
std::chrono::duration<double> elapsed2;
start2 = std::chrono::system_clock::now();
for(int i=0; i<size; ++i) {
result_assert[i] = add2_assert(x, y);
}
end2 = std::chrono::system_clock::now();
elapsed2 = end2 - start2;
std::cout << "using assert: " << elapsed2.count() << " seconds" << std::endl;
// 작업이 다 끝난 후 힙메모리에 할당한 변수를 반드시 지워준다.
delete[] result_exception;
delete[] result_assert;
return 0;
}
이 코드에는 중요한 버그가 하나 있는데, new
를 통해 힙메모리 할당을 해주는 부분에서 결함이 있다.
변수를 함수 호출 초기에 선언한 후 실행이 끝나기 직전에 delete
를 통해서 메모리 해제를 해주도록 하였는데, 달리 말해서 실행이 도중에 멈추면, 즉 delete
를 호출하기 전에 프로그램이 죽는 문제가 발생하면?
프로세스가 죽는 이유로는, 내부적인 문제가 원인이 될 수도 있지만 프로세스 외부의 컨디션 등에 문제가 있어서 프로세스가 죽는 원인이 될 수도 있다. 외부의 문제로 인해 프로세스가 죽는다고 가정해 보면, delete
근처로 가기도 전에 프로세스가 죽을 수도 있고, 그렇게 되면 이 프로그램은 메모리 누수를 발생시킨다. 실제 현업에서도 메모리 누수가 발생하여 주기적으로 메모리를 비워주기 위해 물리적으로 서버를 재부팅 하는 경우가 많다.
이런 문제는 네이키드 포인터를 활용해서는 해결이 어렵기 때문에, 스마트 포인터의 활용이 권장된다.
스마트 포인터는 어떤 원리로 동작하는가?
간단하게 말하자면, 스마트 포인터는 소멸자 안에다가 delete
를 삽입한 개념이다. 즉 함수 안에서 생성한 변수가 스택 프레임이 날라가면서 지워질 때, 자동적으로 소멸자 호출을 통해 메모리를 해제해 주는 것이다. 그렇기 때문에 스마트 포인터를 사용할 때는 예상치 못한 문제로 인해 프로세스가 도중에 죽더라도 메모리 해제는 하고 죽게 된다.
요새 운영체제에서는 메모리 누수를 주기적으로 감지해서 해제해 준다는데?
사실, 요새라고 하기엔 이미 10년 가까이 지난 이슈이긴 하다. 어쨌든 이것을 고려했을 때는 굳이 네이키드 포인터를 사용하는 것을 주의할 필요가 없지 않은가?
다음 경우를 보자.
(1) 운영체제에서 주기적으로 메모리 누수를 해제한다고 하는데, 그 주기보다 프로세스가 죽었다가 다시 실행되는 주기가 더 빠른 경우
(2) 메모리 누수를 운영체제에서 바로잡는데 발생하는 별도의 오버헤드 발생
이를 봤을 때 안정적으로 운영하려면 그래도 스마트 포인터를 쓰는게 맞다.
최종: 위의 코드를 그럼 어떻게 고치나?
우선 스마트 포인터를 이용한 동적 할당을 다음과 같이 선언해 줄 수 있다.
unique_ptr<int32_t[]> result_exception(new int32_t[size]);
unique_ptr<int32_t[]> result_assert(new int32_t[size]);
그리고 나서 delete
부분은 필요가 없어졌으므로 지워주면 된다.
'C++' 카테고리의 다른 글
[C++] 네이키드 포인터 / 스마트 포인터 (unique_ptr, shared_ptr, weak_ptr) (1) (0) | 2022.12.16 |
---|---|
[C++] 두 수의 합/차가 overflow가 발생하는 경우 감지하는 코드 작성 (0) | 2022.12.14 |
[C++] 정수 연산시 오버플로우가 발생하는 문제점 (0) | 2022.12.13 |
C++에서 어마어마하게 큰 숫자를 활용해야 하는 경우 어떻게 할 것인가? (0) | 2016.11.07 |
GCC로 .cpp 파일 컴파일 하는 법 (0) | 2016.04.14 |