(C++) 6.9 메모리 동적 할당 (new, delete)

이준우·2021년 10월 19일
0

동적할당은 까다로운 부분이다. 알아두면 정말 편하므로 힘들어도 한번 알아보고 지나가자!( •̀ ω •́ )✧

#include <iostream>

using namespace std;

int main()
{
	int var;
	int *ptr = new int;

	*ptr = 7; // 역참조를 해줘야 함.


	return 0;
}

우리가 변수를 선언할 때 int var;처럼 선언하는 경우가 대다수였다. 이를 new를 이용하여 선언하면 어떻게 될까?

new를 사용하면 자료형 size에 맞춰서 os에게 메모리를 받아오고 그 메모리 주소를 우리에게 알려준다. 그렇기 때문에 포인터로 받아주어야 한다.

근데 굳이 왜 new를 사용하여 변수를 생성해야 하는 의문이 생긴다. int var;처럼 사용하면 되는 것인데 왜 굳이? 그 이유를 알아보자.

예를 들어 int var[1000000]을 선언했다고 가정하자. 이를 compile하면 문제가 없다. 프로그래머 입장에서도 백만이라는 숫자는 그리 큰 숫자는 아니다. 하지만 이를 debug 하면 알 수 없는 오류가 발생한다. 이를 해결해주는 것도 new이다.

#include <iostream>

using namespace std;

int main()
{
	int var;
	int *ptr = new int{ 7 };
	

	//*ptr = 7;  역참조를 해줘야 함.

	cout << ptr << endl;
	cout << *ptr << endl;

	return 0;
}

output : 013AE210
	 7

초기화 또한 가능하다. 코드에선 uniform initalization을 사용하여 초기화 했다.

더 중요한 것은 할당 받은 메모리를 os에 돌려주어야 한다!! (매우 중요!)

-> 할당 받은 메모리를 돌려줘야 하는 이유는 다음과 같다. 인공지능을 돌리거나 빅데이터를 활용하여 코드를 작성하면 컴퓨터의 메모리를 넘어서는 양을 사용할 때가 종종 있는데, 이럴 경우에 할당받은 메모리를 반환한다면 oom(Out of Memory)가 발생하지 않고 사용할 수 있다.

#include <iostream>

using namespace std;

int main()
{
	int var;
	int *ptr = new int{ 7 };

	cout << ptr << endl;
	cout << *ptr << endl;

	delete ptr;

	return 0;
}

간단한 예를 보면 이렇다. delete를 사용하는 것이다. 이게 무슨 말이냐면 프로그램이 종료되기 전에 할당받았던 메모리를 os에게 다시 돌려주는 것이다. 그렇다면 아까는 사용하지 않았는데 왜 지금은 굳이 사용해야 돼? 라는 의문이 생길 수 있다. 아까 같은 경우에는 os가 어떤 메모리를 사용하였는지 알고 있던 상태이므로 프로그램이 종료되면 자동으로 갖고 가는 경우라서 오류가 생기지 않는다. (지금 단계에서는 아마 데이터 량이 크지 않아서 그렇지 않을까 생각한다.)

그럼 delete를 한 후에는 어떻게 될지 코드로 한번 봐보자.

#include <iostream>

using namespace std;

int main()
{
	int var;
	int *ptr = new int{ 7 };

	cout << ptr << endl;
	cout << *ptr << endl;

	delete ptr;

	cout << "After delete" << endl;
	cout << ptr << endl;
	cout << *ptr << endl;

	return 0;
}

output : 00EA0550
	 7
	 After delete
	 00008123

compiler 에 따라 주소가 같게 나오는 경우도 있고 다르게 나오는 경우가 있는데 다르게 나온다면 나와 같은 상황이다. 이 상황에 대해서는
https://stackoverflow.com/questions/33374483/what-does-visual-studio-do-with-a-deleted-pointer-and-why 을 참고하면 좋다. 결론적으로 말하면 컴파일러 옵션에서 끌수도 있다는 것이다. 자세한건 링크를 참고하자.

나중에는 이 같은 과정을 class를 이용하여 처리한다.

보통은 nullptr을 가지고 사용하는데, 매우 유용하다. 하지만 이 같은 경우는 실제로 많이 사용하진 않는다. 뒤에서 배우는 스마트 포인터라는 개념이 있는데, 그때까지는 "이러한 것도 있구나" 라고 생각하며 넘어가도록 하자.

또한, 아주 간혹가다가 new를 사용하여 메모리를 할당받고 싶은데 그러지 못하는 경우가 있다. os가 모든 메모리를 다 사용하고 있을 경우 말이다. 이런 경우에는 2가지가 있는데,

  1. 프로그램을 죽인다.
  2. 다른 메모리가 메모리를 다 사용할 때까지 기다니는 방법.

1, 2번 같은 경우에는 new로 만든 변수가 사용할 수 있는 메모리가 나타날 때까지 기다려야 하는데, 이를 코드로 짜면 다음과 같다.

int *ptr = new (std::nothrow) int{ 7 };을 사용하면 오류를 발생시키지 않고 사용할 수 있다. 이는 예외처리에 포함된 개념이므로 나중에 예외처리를 공부하면서 다시 복습해보도록 하자!!!

또 코딩을 하면서 가장 무서운게 memory leak

이 문제는 정말 무섭다.

#include <iostream>

using namespace std;

int main()
{
	//memory leak

	while (true)
	{
		int *ptr = new int;
		cout << ptr << endl;
	
	}

	return 0;
}

코드를 보면 할당 받은 메모리를 지우지 않은체 계속해서 새로운 메모리를 받는 것을 볼 수 있다. 실제로 작업관리자를 틀고 위의 코드를 실행하면 빨간색으로 위험하다는 표시를 해주는데, 이를 계속해서 두면 컴퓨터가 고장난다. 이를 방지하기 위해선 항상 delete를 사용하여 방지하여야 한다. 아래의 코드처럼 말이다.

#include <iostream>

using namespace std;

int main()
{
	//memory leak

	while (true)
	{
		int *ptr = new int;
		cout << ptr << endl;

		delete ptr;
	
	}

	return 0;
}

비주얼 스튜디오 에서 보고싶다면은 debug를 하여 확인하면 된다. 만약 오른쪽에 Diagnostic Tools가 없다면 왼쪽 상단 Debug -> window -> Diagnostic Tools를 눌러주면 오른쪽 상단에 생성되는 것을 볼 수 있다.

프로그램을 잘 만드려면 newdelete를 적게 사용하는 것이 좋다. 이유는 os한테 메모리를 받고 다시 주고 ... 받고, 주고 이러면 속도가 느려지기 때문이다.

0개의 댓글