C++

[강의] 10월 31일 수업정리

k-codestudy 2024. 11. 1. 04:12

오늘은 배열의 동적 할당, 메모리 누수 검출 방법, 메모리 관리하는 방법에 대한 수업을 들었다.

 

1. 배열의 동적 할당

동적 할당을 이용할 때, 포인터는 배열과 단일 객체 모두를 가리킬 수 있다. 그렇기에 참조되는 것이 배열인지 단일 변수인지 구분할 수 없으므로, 동적으로 할당된 배열의 주소와 크기를 세트로 관리하는 것이 중요 포인트이다.

 

1.1 예제

#include <iostream>

int main()
{
	int* p{};
	int nLength{};

	scanf_s("%d", &nLength);

	p = new int[nLength] {};

	for (int i = 0; i < nLength; i++)
	{
		printf("%d ", p[i]);
	}
}

동적 할당된 배열을 사용할 때는 항상 배열의 첫 번째 주소값과 크기를 세트로 다뤄야 한다. 그래야 동적 할당된 메모리를 효율적으로 관리할 수 있기 때문이다.

 

1.2 동적 할당된 배열을 해제

그럼 동적 할당된 배열을 해제할 땐 어떻게 해야 하는가?

동적 할당된 메모리가 배열이라면 delete []를 사용해야 한다. 만약 delete만 사용하면 배열의 첫 번째 요소만 해제될 수 있으며, 이는 메모리 누수의 원인이 된다.

delete[] p;
  • 단일 변수 해제: delete p;
  • 배열 해제: delete [] p;

 

2. 메모리 누수 검출 방법

메모리 누수를 검출하는 방법은 두 가지가 존재한다.

  1. 디버그 모드를 이용하는 방법
  2. 메모리 누수를 알려주는 코드를 작성하는 방법

이번에는 디버그 모드에서 메모리 누수 감지 기능을 활성화하는 방법을 이야기해 보겠다.

 

2.1 코드 설정

 

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

먼저, #define _CRTDBG_MAP_ALLOC와 관련 헤더 파일들을 포함을 시켜준다.

그 후, 프로그램 시작 시와 종료 시 메모리 누수를 체크하기 위해, 시작 부분 종료 부분에 설정을 추가해 줄 수 있는데,

 

 

2.1.1 시작할 때 

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

이 코드는 메모리 할당 디버그 플래그를 설정하여, 프로그램 종료 시 자동으로 메모리 누수를 감지하도록 한다.
main 함수 맨 윗부분에 추가해 준다..

 

 

2.1.2 종료할 때 

_CrtDumpMemoryLeaks();

종료 지점에서 누수 정보를 출력하는 명령어지만, 종료 시점이 불확실할 때도 자동으로 메모리 누수를 검출할 수 있기에 잘 사용되지 않는다

 

2.2 실행 결과 예시 (Visual Studio 기준)

만약 p3의 메모리 해제를 생략하면, 아래와 같은 메시지가 출력됩니다:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>

int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	int* p1{};
	int* p2{};
	int* p3{};

	p1 = new int{};
	p2 = new int[5]{};
	p3 = new int{};


	*p1 = 99;
	p2[3] = 100;
	*p3 = 1;

	delete p1;
	delete[] p2;
	//delete p3;
}

 

메시지 

Detected memory leaks!
Dumping objects ->
{82} normal block at 0x0000023B1CA86000, 4 bytes long.
 Data: <    > 01 00 00 00 
Object dump complete.

출력 내용

  • 메모리 할당 번호: {82}
  • 블록 형식: normal block
  • 16진수 메모리 위치: 0x0000023 B1 CA86000
  • 블록 크기: 4 bytes
  • 블록 내 데이터의 처음 16 바이트 : Data: < > 01 00 00 00

이런 식으로 현제 어느 부분에서 할당된 메모리가 몇 바이트만큼 메모리 누수가 일어나고 있다고 알려준다

주의할 점은 Data: <    > 01 00 00 00 부분 컴퓨터는 리틀 엔디안 형식으로 저장(뒤에서부터 읽어 드린다는 것)되므로 실제값 00 00 00 01을 의미한다.

 

3. 메모리 관리하는 방법 ( 외우기 )

메모리를 관리하는 방법은 총 3가지가 존재한다

  1. 메모리 풀 (Memory Pool)
  2. 스마트 포인터
  3. 메모리 관리자

이중 우리는 메모리 관리자로 메모리를 관리할 것이다.

 

 

3.1 메모리 풀

메모리 풀은 큰 메모리 블록을 미리 할당해 두고, 그 안에서 필요한 데이터를 할당 및 해제하는 방식이다.

예전에는 많이 쓰였으나, 메모리 단편화 문제로 인해 비효율적인 경우가 많다.

메모리 풀을 사용하려면 메모리 조각 모음을 수행해야하는데, 메모리 조각 모음은 메모리 공간이 효율적으로 사용될 수 있도록 흩어진 메모리 조각을 정리하는 작업이다. 조각 모음을 수시로 진행해야 하므로 관리 코드가 복잡하다는 단점이 있다.

실제로 메모리 조각 모음을 하지 않으면 효율이 나오지 않는다는 추가 단점도 존재한다.

 

3.2 스마트 포인터

 

스마트 포인터는 포인터처럼 사용할 수 있는 클래스 템플릿으로, 메모리 해제를 자동으로 처리해 준다.

그러나 스마트 포인터를 사용할 경우 메모리가 언제 해제될지 예측하기 어려워 성능에 영향을 미친다. ( 할당 해제가 가장 속도가 느리기 때문 ) 즉, C++의 장점 중 하나인 속도를 포기하게 되는 상황이다.

스마트 포인터는 사용자가 메모리를 해제를 빠뜨릴 위험이 있을 때 보조적으로 사용되곤한다. 
예를 들어, 객체 관리 엔진이 메모리를 할당하고 그 포인터를 외부로 넘겨줄 때, 해제를 보장하기 위해 스마트 포인터를 사용한다. 이는 사용자가 메모리 해재를 빠뜨린 경우, 스마트 포인터가 마지막 방어선 역활을 하여 자동으로 해제하게 되는 방식이다.

 

 

 

3.3 메모리 관리자

메로리 관리자의 핵심은 메모리의 소유권을 명확히 지정하는 것이다.

메모리 관리자는 생성과 해제 시점을 명확히 함으로써 효율적으로 메모리를 관리할 수 있다.

메모리관리자는 주로 빌려주는 매니저, 만들어주는 매니저 두가지 방식으로 사용된다.

 


3.3.1 빌려주는 매니저

스타크래프트 게임의 마린 유닛을 예로 들어보겠다. 마린의 공격력을 높이면 새로 생성된 마린이든, 기존에 있던 마린이든 공격력은 모두 증가된 상태가 된다. 이와 같이 같은 리소스를 공유하고, 필요한 자원을 빌려 사용하는 방식이다. 더 이상 필요 없으면 해제하는 개념이다.

 

3.3.2 만들어주는 매니저 ( 이게 관리가 더 어렵다 )

매모리를 생성하여 사용자가 받아서 사용하는 방식인데 메모리를 만들어서 건내줬을경우 누가 주인이 되는건가?

메모리를 건내받은 사용자가 주인이 되어 버리며,  이 경우, 메모리의 생성과 해제 시점이 다를 수 있어 관리가 까다롭다. 

그렇기에 생성 창구, 해제 창구를 서로 일치시켜 시점을 동기화 시켜줘야한다. ( 만들어 주세요 와 해제해 주세요를 한 창구에 요청을 하게 만들면 되는것이다 )

만약 생성과 해제를 요청하는 창구가 일치하지 않는다면, 동기화 문제가 발생할 수 있다. 

 

'C++' 카테고리의 다른 글

[강의] 11월 5일 수업정리  (3) 2024.11.05
[강의] 11월 1일 수업정리  (0) 2024.11.03
[강의] 10월 30일 수업정리  (2) 2024.10.31
[강의] 10월 29일 수업정리  (1) 2024.10.29
[강의] 10월 25일 수업정리  (0) 2024.10.27