동적 메모리 할당

동적 메모리 할당 또는 메모리 동적 할당은 컴퓨터 프로그래밍에서 실행 시간 동안 사용할 메모리 공간을 할당하는 것을 말한다.  사용이 끝나면 운영체제가 쓸 수 있도록 반납하고 다음에 요구가 오면 재 할당을 받을 수 있다. 이것은 프로그램이 실행하는 순간 프로그램이 사용할 메모리 크기를 고려하여 메모리의 할당이 이루어지는 정적 메모리 할당과 대조적이다.

동적으로 할당된 메모리 공간은 프로그래머가 명시적으로 해제하거나 쓰레기 수집이 일어나기 전까지 그대로 유지된다. C/C++와 같이 쓰레기 수집이 없는 언어의 경우, 동적 할당하면 사용자가 해제하기 전까지는 메모리 공간이 계속 유지된다. 동적 할당은 프로세스의 힙영역에서 할당하므로 프로세스가 종료되면 운영 체제에 메모리 리소스가 반납되므로 해제된다. 그러나 프로세스가 계속 실행될 때에는 동적할당 된 영역은 유지되므로 프로그램이 정해진 힙 영역의 크기를 넘는 메모리 할당을 요구하면 할당되지 않는다. 따라서 사용이 완료된 영역은 반납하는 것이 유리한데, 프로그래머가 함수를 사용해서 해제해야 한다.

동적 할당은 함수가 종료되거나 변수 영역을 벗어나면 자동으로 공간 해제가 이루어지는 스택을 사용한 자동 변수와 대조적이다. 프로세스의 정적 메모리 할당은 프로세스가 시작할 때 이미 정해진 메모리량으로 한정되어있기 때문에, 프로세스가 시작할 때부터 끝날 때까지 유지되는데 반해, 동적 할당은 프로세스의 실행 과정 중에 필요한 메모리를 운영체제에 요구해 할당받고 해제하는 것이 가능하다.

 용어 
 힙 영역 
힙()은 C 언어나 자바와 같은 프로그래밍 환경에서 원시 자료형이 아닌 보다 큰 크기의 데이터를 담고자 동적으로 할당하는 메모리 공간을 지칭한다. 프로그램 코드에서 원하는 크기의 메모리 할당을 요청하면 힙을 관리하는 라이브러리 혹은 모듈이 지정된 크기의 힙 공간 안에서 사용 가능한 곳을 찾아 그곳을 다른 스레드나 프로그램이 사용하지 못하도록 예약 상태로 만들고 접근 가능한 핸들이나 포인터를 반환하는 식으로 사용할 수 있다.

물론 구조체나 객체를 스택에 선언하여 사용하는 것도 가능하지만, 실행 시간에 크기가 결정되는 동적 배열 및 리스트와 같은 경우는 힙을 사용하는 것이 보다 공간을 효율적으로 활용할 수 있다. 대신에 C 언어와 같은 경우 사용한 공간을 명시적으로 반환해주어야 하므로 이 과정을 빠뜨림으로써 메모리 누수와 같은 버그의 원인이 되기도 한다. 메모리 관리를 자동으로 해 주는 언어도 있지만, 이 경우 쓰레기 수집기로 인해 성능이 저하되는 경우도 있다.

 장점과 단점 
 장점: 상황에 따라 원하는 크기만큼의 메모리가 할당되므로 경제적이며, 이미 할당된 메모리라도 언제든지 크기를 조절할 수 있다.
 단점: 더 이상 사용하지 않을 때 명시적으로 메모리를 해제해 주어야 한다.

 동적 할당 방법 

 C 언어 
섬네일|center|600px|C언어에 의한 메모리 맵 예. 일반화 및 단순화 함.
참고:

C/C++로 프로그램 작성 후, 실행파일을 만들어 실행시키면 위와같은 메모리 구조를 갖는다. 동적할당의 경우 힙영역에서 할당되는데, 관련 함수를 통해 동작 할당 요구를 하면 정해진 길이를 전체영역에서 일부분을 할당하고 포인터값을 넘긴다. 힙영역의 메모리 공간관리하는 프로그램이 있어야 하는데, 이것은 각 프로세서마다 자동으로 라이브러리로부터 함수를 링크시켜 힙영역을 관리하는 코드가 프로세서와 결합하여 동작한다.

관련함수는 C 언어의 표준 라이브러리로 stdlib.h에 정의되어 있다.

 함수 기능 void * malloc ( size_t size ); size 바이트의 메모리를 힙에서 할당하여 반환한다. void * calloc ( size_t num, size_t size ); (num * size) 바이트의 메모리를 힙에서 할당하고 포인터값을 반환한다. void * realloc ( void * ptr, size_t size ); ptr이 가리키는 메모리를 size 바이트만큼 힙에서 재할당하여 반환한다. void free ( void * ptr ); ptr이 가리키는 메모리를 해제한다. 해제 전까지 계속 존재하므로 필요없으면 이 함수에 의해 해제해야 한다.

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

int main(int argv, char *arg[])
{
    unsigned char *pdata = 0;
    int size;

    if (argv >= 2)
       size = atoi(argv[1]);
    else {
       printf("할당할 메모리 크기를 입력 : ");
       scanf("%d", &size);
    }
    pdata = (char *) malloc(size);
    if (pdata == NULL) {
        printf("동적할당에 실패 했습니다. 요청크기=%d\n", size);
        return -1;
    }

    // 할당 된 영역을 사용한다.

    // 할당된 영역 사용이 완료되면
    free(pdata );

    return 0;
}

C 언어에서는 malloc, calloc 함수로 동적 할당한다. malloc함수의 인수로 크기값을 넘겨 요청하면 힙영역관리 함수가 영역을 확보한다. 영역에 여유가 있다면 할당하고 그 메모리주소값을 넘긴다. 그러나 실패하면 NULL값을 넘긴다. 사용이 완료되면 해당 주소값을 넘겨 해제하면 재할당이 가능한 상태가 된다. 실제로 메모리에 할당된 크기는 요청한 크기와 다르다. 요청한 길이에 힙관리 데이터가 필요하고 경우에 따라 프로그램이 정해진 규칙에 따라 크기를 할당한다. 따라서 전체 힙영역 크기를 내가 요청한 길이의 합으로 단순 비교하면 크기가 달라질 수 있다.

플랫폼에 따라서 farmalloc, farcalloc와 같은 원거리 포인터 할당 함수를 지원하기도 한다.
realloc 함수로 재할당하며 free 함수로 해제한다.
#include <stdio.h>
#include <conio.h>
#include <malloc.h>

void main()
{
    unsigned char near * pMemory = 0;
    unsigned char far * lpMemory = 0;
    printf("할당할 근거리 메모리 크기를 입력해 주세요. ==> ");
    int size;
    printf("\n");
    scanf("%d", &size);
    pMemory = (char near *)malloc(size);
    printf("할당할 원거리 메모리 크기를 입력해 주세요. ==> ");
    scanf("%d", &size);
    printf("\n");
    lpMemory = (char far *)farmalloc(size);

    printf("원거리 메모리 해제합니다.\n");
    farfree(lpMemory)
    printf("근거리 메모리 해제합니다.\n");
    free(pMemory);

}

 C++ 언어 
언어 차원에서 new, delete 연산자를 통해 동적 할당을 제공한다. 재할당 연산자는 현재까지 없으며, 여전히 C 함수를 사용할 수 있다. new와 delete 연산자는 내부적으로 malloc()과 free()와 유사한 함수를 사용하여 메모리 맵상의 힙영역으로부터 저장공간을 할당한다.

class Test
{
private:
    int _data;

public:
    Test() : _data(0) { }
    Test(int data) { this->_data = data; }

    int getData() { return _data; }
};

int main()
{
    Test * pMemory = 0;

    cout<<"할당할 메모리 크기는? == ";
    int size;
    cin>>size;
    cout<<endl;

    pMemory = new Test[size];

    // 확보한 공간을 사용한 후...

    cout<<"할당된 메모리가 해제됩니다."<<endl;

    delete[] pMemory;

    pMemory = new Test(10);
    cout << pMemory->getData() << endl;
    delete pMemory;
}

 C++/CLI 언어 
new 연산자 이외에 닷넷 기반의 관리되는 힙에도 동적 할당을 할 수 있다. 이 경우 연산자는 gcnew이다. 해제는 delete 연산자로 동일하다. 
관리 힙에 할당하려면 형식이 관리되는 형식이어야 한다.

using namespace System;

namespace NetTest
{
    public ref class Type
    {
        private:
        unsigned long _data;
        public:
        Type(unsigned long data) { this->_data = data; }

        property unsigned long Data
        {
            unsigned long get() { return this->_data; }
        }
    }
}

void _tmain()
{
    ::NetTest::Type^ pType = nullptr;
    cli::array<::NetType::Type ^, 1>^ pTypeArray = nullptr;

    Console::Write(L"할당할 메모리의 크기는?");

    unsigned long size = Console::Read();

    Console::Write("\n");

    pTypeArray = gcnew cli::array<::NetType::Type ^, 1>(size);

    Console::WriteLine(L"할당된 메모리가 해제됩니다.");
    delete[] pTypeArray;

    Console::WriteLine(L"단일 객체가 생성됩니다.");
    pType = gcnew ::NetTest::Type();
    Console::WriteLine(L"할당된 객체가 해제됩니다.");
    delete pType;
}

 C# 언어 
기본적으로 클래스와 인터페이스 형식의 인스턴스는 모두 동적 할당된다.

 같이 보기 
 정적 메모리 할당
 메모리 관리
 포인터 (프로그래밍)

분류:메모리 관리
분류:컴퓨터 구조