게임학원, 게임프로그래머 취업 전문 교육기관 DirectX11/12 자체엔진 게임개발과정,서버프로그래밍,자료구조,알고리즘,유니티,언리얼 게임학원, 언리얼학원 동적할당
본문 바로가기

동적할당

동적 할당의 필요성

int main()
{
    int iArr[10] = { 0 };

    return 0;
}

 int 자료형 으로 10개 짜리 배열을 선언했습니다. 우리가 이 배열에 데이터를 저장한다고 했을 때,

10 개의 공간은 모자랄 수도 있고, 남아 돌 수도 있습니다. 

따라서 상황에 맞게 우리가 입력한 값으로 가변적으로 배열을 할당 할 수 있으면 좋겠다는 생각이 듭니다.

그래서 다음과 같이 코드를 변경해 보겠습니다.

int main()
{
    int iCount = 0;

    scanf_s("%d"&iCount);   // 입력 대기

    int iArr[iCount] = { 0 }// 입력한 값으로 배열의 크기를 정함.

    return 0;
}

하지만 위의 코드는 문제가 있습니다.

첫 번째로 iArr 배열은 지역 변수로서 스택 영역에 저장이 됩니다. 이러한 스택 영역에 할당되는

지역 변수들은 그 크기가 컴파일 시점에서 이미 정해져 있어야 합니다.

 

스택의 구조 상, 함수가 호출되고 종료될 때 이전 상태로 복귀해야 합니다.

각 함수들은 자신이 사용할 메모리의 크기를 미리 알고 있어야 한다는 것이지요.

 

하지만 위 코드는 사용자가 입력하기 전까지 배열의 크기가 얼마인지 알 수가 없습니다.

따라서 컴파일러는 위 코드를 어떻게 번역해야 할지 알 수가 없게 됩니다.

 

따라서 배열의 크기를 넣는 곳에 상수 값을 넣어달라고 컴파일러 오류를 발생시킵니다.

 

두 번째 문제는 배열에 여기에 자료를 순차적으로 저장한다고 했을 때,

할당된 공간을 다 쓰게 되면 우리는 공간을 확장 할 필요가 있습니다.

 

하지만 고정적인 공간을 할당할 수밖에 없는 지역변수의 특성 상 이러한 문제를 해결할 수가 없습니다.

이러한 문제를 해결하기 위해 스택 영역이 아닌 힙에 사용자가 원하는 만큼 유동적으로 메모리를 할당 할 수 있는

동적할당 이 필요하게 됩니다.


C 의 동적할당 방법


int* pData = (int*)malloc(sizeof(int)); // (inr*)malloc (4) 와 다름 없다.free(pData);

 C 에서는 malloc 이라는 함수를 써서 동적 할당을 합니다.

malloc 은 힙 영역에 할당 할 바이트의 크기를 인자로 요구하고, 그 시작 주소를 void* 로 반환합니다.

여기서 포인터에 대해 다시 설명 하자면, 포인터에 타입이 있는 이유는, 해당 주소에 가면 어떤 데이터가 있는지

명시하기 위함입니다.

malloc 이라는 함수는 범용 적으로 사용되어야 하기 때문에 힙에 할당한 데이터의 주소를 받아가는 쪽에서 

포인터 타입을 결정 할 수 있도록 반환 타입을 void* 타입으로 설정했습니다. 

따라서 사용자가 자신에게 알맞은 포인터 타입으로 캐스팅해서 사용합니다.

(malloc 함수 입장에서는, 동적할당 된 공간이 어떤 용도로 사용 될 지 모르기 때문에, 호출자가 직접 포인터 타입을 정하라는 이야기입니다.)

이렇게 힙 영역에 할당 된 메모리는 사용자로 하여금 스스로 메모리를 관리해야할 의무를 가지게 합니다.

스택메모리처럼 스스로 해제되지 않기 때문에, 또한 사용자가 그것을 바랬기 때문에, 힙 영역에 동적

할당한 메모리는 우리가 알맞은 때에 직접 해제하지 않으면 안 됩니다.

 

이렇게 동적 할당한 메모리를 해제하는 함수가 바로 free 입니다.

인자로 우리가 할당한 메모리의 시작 주소를 넘겨주게 되면, 그 메모리를 반환하게 됩니다.

 

할당한 메모리를 해제하지 않고 프로그램을 종료하게 되면, 메모리 누수 현상이 발생합니다.

따라서 반드시 힙 영역을 사용 할 시에는 그 관리에 주의하여야 합니다.

 

malloc 이외에도 calloc 이라는 함수가 있습니다.

이 둘의 차이점은 동적 할당한 메모리를 초기화를 해주는지 아닌지의 차이입니다.

 

가령 배열을 동적할당 한다 했을 때, 코드는 다음과 같습니다.

int* pData = (int*)malloc(sizeof(int) * 10);

memset(pData, 0sizeof(int) * 10);

free(pData);


int 10개짜리 배열을 할당하고 있습니다. 힙에 할당 된 메모리의 초기 값이 보통 쓰레기 값이라고

표현을 하는데요. 정확하게는 다음과 같습니다.

16진수 0xCD 40바이트가 채워져 있는 것을 볼 수 있습니다.

우리가 흔히 쓰레기 값으로 알고 있는 0xCD 의 의미는 디버깅 모드에서, 막 동적 할당된 곳에 채워지는 값입니다.

우리는 배열의 초기 값을 0으로 세팅하길 원하기 때문에 memset 이라는 함수를 써서 0으로 이 공간을

초기화 하고 있습니다.

 

그렇다면 calloc 함수는 어떤 함수인지 짐작이 가시나요? 이 함수는 내부적으로 힙에 할당 한 메모리를

0 으로 채워줍니다. 즉 우리가 귀찮게 일일이 memset 을 매번 호출하지 않아도 되는 것이지요.


C++ 의 동적할당 방법

int* pData = new int;delete pData;

 C++ 에서는 malloc 이라는 함수를 대체하는 연산자가 바로 new 입니다.

malloc 처럼 할당 할 크기를 인자로 넘기는 것이 아니라, 할당 할 크기의 자료형을 제시합니다.

int 자료형의 크기가 4 바이트 라서, 힙 영역에 4 바이트를 할당하고, 그 시작주소를 반환하라는 의미입니다.

free 함수를 대체하는 연산자는 delete 입니다.

free 함수처럼 그 시작주소를 인자로 넘기지만, delete 연산자는 연속되는 메모리(배열을) 삭제할 때에는 

delete[] 로 호출해야 합니다.

배열을 동적할당 하고, 해제하는 코드는 다음과 같습니다.


int* pData = new int[10];  

delete[] pData;


C 와 C++ 의 동적할당 차이점

그렇다면 malloc, free new, delete 의 차이점은 무엇일까요?

첫 번째 큰 차이점은 바로 함수냐 연산자냐의 차이일 것입니다.


 malloc free 는 함수입니다. 반면에 C++에서 사용하는 new delete operator,

, 연산자 입니다. 그리고 C++ 에서는 이러한 연산자를 재정의 하거나 오버로딩을 통해서 추가기능을

만들 수 있습니다.

 

, 우리가 new delete 를 구현 할 수 있다는 것이지요.

 

C++ 레퍼런스에서는 대체 가능한 new delete 연산자 함수를 제시하고 있습니다.

replaceable allocation functions

 

 

void* operator new  ( std::size_t count );

(1)

 

void* operator new[]( std::size_t count );

(2)

 

void* operator new  ( std::size_t count, const std::nothrow_t& tag);

(3)

 

void* operator new[]( std::size_t count, const std::nothrow_t& tag);

(4)

 

  

그렇다면 우리가 만든 new operator 함수를 사용하도록 해볼까요?

void* operator new(std::size_t size)

{    

    return malloc(size);

}


int main()

{    

    int* pData = new int;

    delete pData;

}

이제부터 new 연산자를 호출하게 되면 우리가 만든 new operator 함수를 호출하게 됩니다.

내용은 심플합니다. 결국 내부적으로 malloc 함수를 호출해서 원하는 바이트 크기만큼 동적할당

하고 있는 것이니까요.

 

자료형을 써주는데 그 크기가 저 함수에 어떻게 알아서 넘어 가냐구요?

C++ 컴파일러가 코드를 번역할 때 알아서 그 크기를 new operator 함수에 넣어줍니다.

int* pNumber = (int*) new int;

00C72B10  push        4  00C72B12  call        operator new (0C71316h)  

어셈블리 코드에서 보면 new int 의 코드를 push 4 라고 인자로 넘기고 함수를 호출하도록 번역 된걸

볼 수 있습니다.

  

두 번째 차이점은 바로 C++ 의 클래스 생성자 호출 유무입니다.

 malloc 으로 클래스를 동적할당 할 경우 생성자가 호출되지 않지만, new 로 할당 하게 되면

생성자를 호출 합니다.

 

new 는 어떻게 생성자를 호출할까요? 우리가 재 정의한 operator new 함수 내에서도 딱히

클래스들의 생성자를 호출하는 부분은 전혀 없습니다.

 

이 원리는 바로 어셈블리 코드를 보면 알 수 있습니다.

int main()

{    

    TestClass* pClass = new TestClass;    

    delete pClass;

}

위의 TestClass를 동적할당 하는 코드가 어떻게 어셈블리어로 전환되었는지 확인해 보면 아래와 같습니다.

00D42B02  call        TestClass::operator new (0D41465h)  

00D42B07  add         esp,4  

00D42B0A  mov         dword ptr [ebp-1FCh],eax  

00D42B10  mov         dword ptr [ebp-4],0  

00D42B17  cmp         dword ptr [ebp-1FCh],0  

00D42B1E  je          main+73h (0D42B33h)  

00D42B20  mov         ecx,dword ptr [ebp-1FCh]  

00D42B26  call    TestClass::TestClass (0D41460h)  

00D42B2B  mov         dword ptr [ebp-234h],eax  

00D42B31  jmp         main+7Dh (0D42B3Dh)  

00D42B33  mov         dword ptr [ebp-234h],0  

00D42B3D  mov         eax,dword ptr [ebp-234h]  

00D42B43  mov         dword ptr [ebp-1F0h],eax  

00D42B49  mov         dword ptr [ebp-4],0FFFFFFFFh  

00D42B50  mov         ecx,dword ptr [ebp-1F0h]  

00D42B56  mov         dword ptr [pClass],ecx  

 파란색으로 표시된 부분을 보면 operator new 가 끝난뒤 생성자를 호출해주는 코드가 알아서 삽입되어

있는 것을 볼 수 있습니다.

 

즉 우리가 신경 쓰지 않아도 new를 통해서 클래스를 동적할당 할 경우 컴파일러가 알아서 생성자를

호출하는 코드를 삽입해 주고 있음을 확인 했습니다.

 

당연히 malloc 으로 생성 할 경우 생성자를 호출해 주지 않습니다.

이부분은 여러분들이 직접 확인해 보세요~

 

- by Raimondo