본문 바로가기

별걸다하는 IT/프로그래밍언어

[C/C++언어 강좌] 동적할당 언제 사용하나요? 동적할당이 뭔가요? (Dynamic allocation)

[C언어 C++언어 프로그래밍 포스팅 링크 목차]

안녕하세요. 양햄찌 블로그 주인장입니다.

오늘 포스팅할 주제는 동.적.할.당! 입니다. 매우 중요한 파트 중 하나죠.ㅎㅎ

동적할당 언제 해??

■ 첫 번째

일시적으로 갑자기 많은 메모리를 잡아야 할 경우가 있어요,

뭐,, 세컨더리 디바이스인 D드라이브나 C드라이브 같은 경우는 메모리를 1테라 이렇게 크게 확보할 수 있지만,

우리가 프로그램을 실행하면 그 프로그램은 일단 RAM에 올라가서 돌게 되죠.

RAM의 메모리 자원은 한정적입니다.

 

그런데 어떤 프로세스가 많은 메모리가 필요한대 이를 프로세스 처음부터 끝까지 계속 홀딩하고 있다면, 그만큼 메모리 효율성은 떨어지겠죠?

사용할 때에만 잠시 잡았다가 필요 없으면 해당 메모리를 해지시켜 다른 곳에서 활용할 수 있도록 하는게 리소스 관리에 효율적입니다. 내가 원할 때, 메모리를 할당했다가, 필요 없어지면 해지시킬 수 있도록 메모리 할당하는 게 동적할당입니다.

50KB이상이면 동적으로 할당해줘야지 아니면 crash가 발생할 위험이 있습니다. 물론 이는 플랫폼에 따라서 더 낮아질 수도 있고 더 높아질 수도 있어요.

 

 두 번째

메모리적인 이유 외에도 동적할당을 사용하는 이유는 존재합니다.

일반 변수나, 함수의 경우 stack이라는 메모리 공간에 할당되는 데, 얘네는 범위를 벗어나면 파괴되어 사용할 수 없어요. 하지만 동적할당으로 잡은 메모리는 stack에 저장되지 않기 때문에 이 영향을 받지 않습니다.

이렇게 함수 리턴 이후에도 메모리 할당이 살아있게 하고 싶으면 동적 할당을 써줍니다.

 

If you need to allocate a large block of memory (e.g. a large array, or a big struct), and you need to keep that variable around a long time (like a global), then you should allocate it on the heap.

꽤 큰 크기의 배열이나 structure들을 써야 하는데, 마치 전역변수처럼 꽤 긴 시간 동안 사용해야 한다면, 힙에다가 할당하는게 좋습니다 (물론, 사용하고 나서는 바로 해제해줘야해요)

 

세 번째

출처;https://www.codesdope.com/c-dynamic-memory/

자, 만약 미리 길이를 알 수 없는 배열을 저장해야 한다해볼게요.

뭐가 들어올지 모르는데,,, 크기를 100으로 잡았다가 혹시나 100이상의 데이터가 들어오면 왼쪽 그림처럼 공간 부족 사태가 벌어지겠죠..? 

그런데 이렇게 될까 무서워 크기를 넉넉하게 잡아놨는데 막상 들어온건 1바이트라면,,, 저렇게 공간낭비가 돼요.

이렇듯 일반 배열 선언에서는 동적으로 길이를 변화시키거나 미리 계산하기는건 어렵죠

동적 할당은 그 때 그 때 필요할 때, 메모리를 요청할 수 있어요.

 

사실 우리가 사용하고 있는 많은 기능들이 (ex C++의 vector라던가..) 이미 동적할당으로 구현되어 있어요,

또 동적할당을 몰라도 충분히 프로그램을 짜실 수 있습니다. 하지만 유용한 기능이니 활용할 수 있다면 좋겠죠.

 

위의 설명이 무슨 의민지 자세히 모르겠다면 걱정하지 마세요~~ 

이제 앞으로 실습을 통해서 차근차근 확인할거니까요 ㅎㅎ

프로세스의 메모리 구조

동적할당은 메모리와 관련되어 있기 때문에 메모리 구조에 대해 알고 있어야 합니다.

출처:http://tcpschool.com/c/c_memory_structure

사실 이 부분에 대해서는 아래 포스팅에서 매우 자세하세 설명을 진행했었어요.

잘 모르시는 부분은 아래 부분을 꼭 확인하고 옵시다.

▼ 프로세스 메모리 구조 포스팅: https://jhnyang.tistory.com/32

 

[운영체제]Process Address Space 메모리내 프로세스 구조 (Code segment, static data 등)

운영체제 목차 이전 포스팅에서 프로세스와 프로그램의 차이를 알아봤어요! 프로세스와 프로그램 차이 요약 다시 정리하면 프로그램은 실행 파일로, 파일 시스템으로 존재하는 것이 실행파일��

jhnyang.tistory.com

 

요약하자면

지역변수와 매개변수 데이터들은 stack이라는 메모리 공간에 생성되어, 스코프가 끝나는 지점에서 메모리는 파괴됩니다.

하지만 동적할당 STACK이 아닌 HEAP 메모리에 생성되기 때문에 스택의 특징을 받지 않습니다.

HEAP 메모리는 유저가 직접 관리합니다. 내가 할당하고 직접 해제하기 전까지는 공간을 차지하게 되는거죠~~~. 

동적 할당 사용법

동적 할당은 두 가지 방법으로 진행할 수 있습니다.

1. C언어의 malloc

2. C++언어의 new  (물론 C++에서 C언어도 지원되기 때문에 malloc 사용 가능)

 

[사용법 비교]

//헤더파일 생략
int main()
{
    // 1-1. malloc 사용해 동적할당
    int* pnum1 = (int*)malloc(sizeof(int));
    // 1-2. 메모리 해제
    free(pnum1);
    
    // 2-1. new 사용해 동적할당
    int* pnum2 = new int; 
    // 2-2. 메모리 해제
    delete pnum2;
    
    return 0;
}

malloc과 new 모두 메모리 할당을 성공하면 해당 메모리의 주소 값을 리턴해줍니다.

실패시 malloc은 NULL, new는 bad_alloc을 리턴해요.

 

malloc이던 new이던 메모리 할당시

우항의 의미는: 내가 heap에 가서 너가 요청한 크기만큼 공간을 잡은뒤 그 시작 주소를 알려줄게!

좌항의 의미는: 주소를 저장할 수 있게 포인터 변수 하나 선언하고

연산자(=):  우항에서 받은 메모리 위치를 좌항의 변수에다가 대입하자!

 

다만 malloc은 주소 값을 void* 타입으로 리턴해주기 때문에 형변환을 명시해줘야 합니다. --> (int*)를 붙여준이유

new는 타입safe로 제작(?)이 됐어요. 그래서 따로 명시해주지 않아도 됩니다. ㅎㅎ

 

malloc의 인자로는 메모리 할당할 크기를 명시해줍니다. 

new는 할당할 사이즈의 타입을 적어주면 돼요.

 

더욱더 자세한 내용은 malloc과 new각각의 포스팅에서 살펴보도록 해요.

동적할당시!

암튼 동적할당 했을 시 메모리적으로 그림을 그려 이해하면 요렇게 됩니다.

포인터 변수의 pnum1과 pnum2는 지역변수이기 때문에, 스택에 생성되고

malloc이나 new로 리턴받은 메모리 주소 값은 heap에 생성된 주소입니다.

 

malloc과 new는 완전 동일한 것은 아닙니다. 위에 기재한 것 이외에도 많은 차이가 있어요.

malloc과 new의 차이점이 궁금하면 아래 포스팅을 참고해주세요

 

조심해야 할 것은 1-2와 2-2인데요.

할당을 해놓고 해제를 시키지 않으면 메모리 누수가 일어납니다. 

메모리 누수에 대해서 (memory leak)

메모리 할당과 관리는 사용자가 직접 관리해야해서 특히!! 더 문제가 발생하기 쉬워요.

필요하지 않은 메모리를 계속 점유하고 있는 현상을 '메모리 누수'라고 합니다.

참고로 자바의 경우 사용하지 않은 메모리를 알아서 해제해주는 가비지 컬렉터(garbage collector)기능이 있으나,

C와 C++은 없어요..

 

꼭 꼭! 동적할당을 했으면 해지를 시켜줘야 합니다.

 

malloc으로 할당했으면 꼭 free로 해제해주기,

new로 할당했으면 꼭 delete로 해제해주기

 

메모리 누수 현상은 해결하기 가장 힘든 작업 중 하나로 꼽힙니다. 일단 해제를 안해줘도 컴파일은 잘 돼요. 발견하기 쉽지 않죠. 또 내가 free나 delete를 다 해줬다 해도, 메모리 누수가 발생할 수 있습니다.

//메모리 누수 예시
#include <iostream>
using namespace std;
int main()
{
    int* pnum = new int;  
    cout << "1. heap메모리 위치 :" << pnum << endl;
    pnum = new int;
    cout << "2. heap메모리 위치 :" << pnum << endl;
    delete pnum;
  
    return 0;
}

첫 번째 동적할당으로 받은 메모리 위치를 pnum에 저장했어요. 

이후 대입으로 다른 값을 넣어버리면, 처음 할당받은 heap메모리 위치에 pnum으로 접근했는데, 접근할 방법이 유실(?)되기 때문에 메모리를 해제할 방법이 없습니다.

보시면 처음 pnum에는 00E39978이라는 heap주소값이 들어갔는데, 얘를 해제해주지 않고

다른 값을 대입해버림으로써 00E35D40으로 바뀌어버렸어요. 이 첫 번째 00E39978를 해제해줘야 하는데, pnum값이 변경해버렸으니 해지할 방법이 없는거죠.

 

참고로 이런 메모리 누수현상으로 발생하는 문제가 심각하다보니..

최근에는 이러한 로우레벨 연산을 좀 피하는 경향이 있습니다.

 

알아서 동적할당으로 메모리 관리를 해주는 컨테이너 vector같은 것들을 쓰거나,

최근에는 unique_ptr이나 shared_ptr같이 안쓰면 자동으로 메모리를 해제해주는 스마트 포인터를 사용해요.

하지만 기존에 많은 소스들이 이미 동적할당 함수를 사용해 구현되어 있으며,

스마트 포인터는 최근에서야 생긴 기능으로 과거버전은 지원되지 않죠.

개발자라면 메모리 내부 처리 과정을 이해하고 있어야 하기 때문에 꼭 잘 알아둡시다.

 

오늘은 여기까지 개략적으로 동적할당에 대해서 살펴보았어요.

다음 포스팅에서는 malloc , new, 그리고 둘의 차이까지~! 차근차근 하나씩 파보도록 해요.

열심히 아는 지식 내에서 작성해보았어요. 도움이 되셨다면 공감 감사드립니다.

다음 포스팅에서 봐요~!