C++ 메모리 누수
목차
메모리 누수(Memory Leak)라는 용어에 대하여 들어본 적이 있습니까?
자바 처럼 가상 머신(Java Virtual Machine)을 사용하는 언어에서는 보통 별로 신경쓰지 않아도 되는 일입니다. 메모리 누수는 유추적인 표현으로 메모리에 문제가 생겨서 줄줄 세는 것을 의미합니다.
이 유추(Ananlogy)적인 아이디어가 컴퓨터 소프트웨어를 이해하는데 거의 결정적인 역할을 합니다. 허나 컴퓨터 소프트웨어 하면 그냥 앉아서 노가다 처럼 코드를 타이핑하는 일로 알려져 있어서 보통의 학생이 아이디어를 잡는 것은 상당히 어려운 일에 속합니다.
이번 포스팅에서 메모리 누수라는 비교적 작아 보이는 주제 하나만 택한 것은 이 Analogy에 대해서 더 알아보기 위해서 입니다.
메모리의 한계
변수(Variable)를 가르칠 때 보통 박스 개념으로 시작합니다. A라는 이름이 쓰여진 상자 하나에 10이라는 숫자를 넣었습니다. int A = 10; 이죠? 이런 식으로 int B; int C; 처럼 얼마든지 박스를 만들 수 있습니다. 메모리 공간이 허락하는 한도내에서 가능합니다.
그럼 극단적인 모델을 생각해보겠습니다. 컴퓨터의 메모리가 40바이트입니다. 4바이트 정수형 박스는 10개 들어갑니다. [4 * 10 = 40 바이트] 초등학교만 졸업해도 여기까지 이해 못할 사람은 없을 겁니다.
10개의 박스가 있다면 이 박스를 사용해서 계산도 하고 여러가지 할 수있는게 많은데요. 5개 박스에 홀수를 다섯개 저장하고(1,3,5,7,9) 한개 박스에 이들의 합(sum)을 구해 넣습니다. 총 6개를 사용했으니 4개가 남습니다. 이제 4개의 박스를 가지고 뭘 할 수 있을까요? 3개의 짝수(2,4,6)에 하나의 합(sum)을 넣을 수 있습니다. 이제 메모리 10개를 다 사용했습니다.
메모리에 10개의 제한이 걸려있다는 것을 알았습니다. 매우 평면적인 개념이지만 현재의 PC는 대부분 기가 단위 메모리를 사용하기 때문에 이런 제한이 있다는 것 조차 경험하지 못한 사람들이 대부분입니다.
제한을 느끼는 것은 메모리 부족 현상으로 프로그램이 다운되었을 때 입니다. ‘아! 메모리라는게 무한대로 사용할 수 없는 거 였구나~ 라고 느낄 때 비로소 메모리의 효율적 사용에 대한 필요가 생깁니다’
동적 메모리 할당
동적 메모리 할당 방식(Dynamic Memory Allocation)은 메모리를 효율적으로 사용하게 합니다. 앞서 살펴본 40바이트의 메모리 모델에서는 4바이트 정수를 10개만 사용할 수 있습니다. 그런데 그냥 쭉 늘어놓으면 금방 동이나죠. 최대한 사용해도 9개 숫자밖에 더하지 못합니다. 이렇게 해서는 컴퓨터를 사용해서 계산하는 의미가 없습니다.
동적 메모리 할당 방식은 이 40바이트를 가리키는 포인터를 별도로 사용합니다. 보통은 스택 메모리에 포인터 변수를 사용합니다. 예를 들어 9개의 메모리를 포인터에 할당해서 나머지 1개의 변수에 합을 저장합니다.
이제 9개 메모리를 해제하고 다시 새로운 9개의 값을 할당합니다. 나머지 1개 변수에 가산(기존의 합산에 더함)합니다. 이런 식으로 메모리를 할당하고 해제하는 방식을 사용하면 100개 이상의 숫자라도 합산할 수 있습니다. 4바이트 범위 (양수 42억)까지 합산이 가능합니다.
즉 메모리는 40바이트지만 얼마든지 연산을 할 수 있다는 말입니다. 만약 정직하게 메모리를 쭈욱 늘어놓고 계산한다면 100개의 수를 합산할 때 400바이트가 필요합니다. 동적 메모리를 사용하면 40바이트로 끝낼 수 있는 것이죠.
매우 단순화한 예이지만 실제 CPU가 작동하는 방식과 유사합니다. 실제 인텔 CPU x86 계열의 명령어 세트는 몇개 안되는 범용 레지스터와 각종 레지스터를 사용해서 모든 연산을 처리합니다.
리누스 토발즈 같은 시스템 프로그래머들이 원초적인 C언어를 선호하는 것은 고수준 언어를 사용하면서 하드웨어적인 수준에(Low Level) 가장 가깝게 다가갈 수 있기 때문입니다.
이게 C++의 어려움인데요. 사실 C++은 처음 언어로 시작하는게 좋다는 전문가들이 많지만, 그들도 C와 같은 원초적인 프로그래밍 언어부터 시작했다는 거죠. 그런 이야기를 들으면 약간 초보자 능욕, 가진 놈들이 더 한다 같은 의미로 받아들여도 크게 무방합니다.
엔지니어들은 한편 숙련된 테크니션이기도 합니다. ‘방망이 깎는 노인’ 같은 문학 작품에서 보듯이 기술자들은 때로 고집이 쎄서 초보자들을 주눅들게 하죠. 이게 프로그래밍에도 전통적 장인과 도제의 개념이 아직은 남아있는 것 같습니다. 21세기 초반부인 지금은 아직도 컴퓨터 공학의 과도기가 아닐까 생각합니다.
동적 메모리 할당이 가진 메모리를 늘이고 줄인다는 아이디어는 딱히 동적인 것만 적용되는 것은 아닙니다. 예를 들어 C++에서 함수는 스택 메모리를 사용하는데 함수를 사용하는 동안은 메모리를 사용하다가 함수가 종료되면 그 함수의 호출자에게 제어권을 넘기면서 메모리를 반환합니다.
단지 컴파일러가 이 일을 수행하기 때문에 프로그래머가 딱히 신경쓰지 않는 것 입니다. JVM(자바 가상 머신)은 동적 메모리에 대한 관리까지 합니다. 사용자는 new 키워드만 사용하고 C++처럼 delete는 사용하지 않습니다. 기술이 발달하면 앱개발자는 동적 메모리 할당이란 개념을 몰라도 될 날이 올 것 같은데요.
그래도 여전히 시스템 레벨에서 프로그래밍을 하고 싶다면 알아야 하는 개념입니다. 누누히 언급하지만 이것을 배운다고 되는것도 아니고 머리속에 이미지와 모델을 그려야 한다고 했습니다.
메모리에 대한 가상의 모델을 머리속에 가지고 있는 것과 없는 것은 전혀 다른 이야기입니다. 그래도 최근에는 인터넷에서 좋은 설명이 있는 문서들과 무료 강의(유튜브, 유데미 등)들이 깔려 있으니 열심히 구글링해서 찾아 보면 어느 정도 도움이 될 것입니다.
메모리 누수
메모리 누수(Memory Leak)가 꼭 동적 메모리 할당에만 일어나는 일은 아닐 겁니다. 허나 C++에서는 프로그래머가 직접 동적 영역을 컨트롤 하다 보니 발생시킬 수 있는 오류입니다. 치명적 오류긴 한데 더 큰 문제는 어디가 문제인지 밝히지 못할 때 그 위험이 더 커집니다.
메모리 누수를 위의 Analogy로 설명하면 10개의 변수를 사용할 때 동적 할당으로 9개를 사용하고 나서 해제를 하지 않고 포인터를 삭제하는 것 입니다. C++이 메모리를 관리하는 방식은 포인터를 통해서입니다. 포인터를 잃어버렸을 경우 어디를 delete 할지 알 수 없으므로 그 메모리는 영원히 잃어버리게 됩니다. 9개에 대한 포인터를 잃어버리면 남은 공간은 1개죠. 이 때 프로그램이 운영체제에게 5개 변수를 사용할 메모리를 할당해달라~ 고 요청하면 프로그램은 종료하게 됩니다.
값에 의한 참조(pass by value)를 사용하는 프로그래밍 방식에서 이런 일은 의외로 쉽게 일어납니다.
메모리 누수는 사실 언제라도 발생할 수 있습니다. 운영체제가 너무 많은 프로그램을 한번에 로드하다 보면 메모리를 초과하는 일이 일어나는데요. 최근의 윈도우 버전같은 현대 운영체제는 가상 메모리를 사용하기 때문에 좀체 오류로 떨어지진 않아도 시스템이 느려지는 일은 늘 일어날 수 있습니다.
네트워크에서 백엔드 프로그램을 작성할 때 문제가 더 큰데요. 서버가 클라이언트 요청에 답하는 소켓 인스턴스를 생성하는 방식에서 한번에 너무 많은 연결을 받게 되면 급속히 메모리 자원이 사라지기 때문에 인스턴스 자체가 다운될 위험이 있습니다. 접속 폭주로 웹사이트가 다운되는 경우는 흔하게 일어나는데요. 보통 그 웹사이트에 할당된 리소스(메모리)가 바닥나기 때문입니다. 디도스 공격의 원리와도 같은데 원리적으로는 어떤 웹사이트라도 디도스를 걸어서 떨어뜨릴 수 있습니다. 공격하는 측도 비용이 들고 위험에 노출되기 때문에 안하는 것 뿐이죠. 죽자고 달려들면 언제라도 가능한게 디도스입니다. 그래서 특정 누리꾼 집단이 싸울 때 디도스를 많이 씁니다. 가장 원초적이고 가장 효과적인 방법입니다.
메모리 누수를 방지하고 효율성을 높이면 저런 문제들에 좀더 오래 버틸 수 있는 프로그램을 만들 수 있습니다. 뭐 네트워크는 그렇다 쳐도 일단 독립된 시스템에서 메모리 관리하는 방법을 잘 알아둘 필요가 있습니다.
요약
프로그래밍 언어로써 C++을 선택한 것은 포인터를 사용하면 최대의 효율을 얻을 수 있기 때문입니다. 나중에 인공지능이 발전해서 포인터와 메모리 관리 조차 컴파일러가 알아서 할 날이 올지도 모르겠습니다. 누군가는 연구하고 있겠죠. 그러나 현재까지 C++이 건재한 것을 보면 아직 인간의 설계와 아이디어가 더 뛰어나다고 믿고 있는 것 같습니다.
C++ 메모리 누수는 하나의 연구 주제로써 깊이 있게 들어가기 좋습니다. 여기 포스팅 내용은 그저 대강스런 내용에 지나지 않습니다.
비주얼 스튜디오에도 memory leak detector – 메모 누출 탐지기 기능이 있고 C++에는 자동으로 메모리를 해제하는 스마트 포인터라는 것도 있습니다. 또 인터넷에는 각종 소스가 있습니다. 더 깊이 있게 알고 싶다면 한번 파보는게 좋습니다.
참고문서
Memory leak in C++ and How to avoid it? – GeeksforGeeks
스마트 포인터(최신 C++) | Microsoft Docs
SMART POINTERS in C++ (std::unique_ptr, std::shared_ptr, std::weak_ptr) – YouTube