프로그래밍 C언어

동적 메모리 할당 (Dynamic Memory Allocate)

게임첫걸음 2024. 8. 18. 22:04

오늘은 동적 메모리 할당에 대한 글을 쓰겠습니다. 메모리 할당에는 크게 2가지로 정적, 동적 메모리 할당으로 나뉩니다. 컴퓨터에서 메모리와 관련된 단기 기억 장치인 RAM에서 처리되고 RAM을 구성하는 3가지 요소가 있으며 각각 DATA, STACK, HEAP으로 나뉘어있습니다.

  • RAM(Random Access Memory): 데이터가 저장된 주소 어디든 접근할 수 있는 것을 뜻합니다. (그냥 엄청난 기술이라고 생각합니다.)
  • DATA: 전역 변수
  • STACK: 지역 변수
  • HEAP: 사용자가 직접 관리

전역 변수와 지역 변수는 지난 글들에서 다룬 바가 있습니다. 이들은 정적 메모리들에 해당되고 DATA와 STACK에서 다루고 있습니다. 오늘의 동적 메모리 할당은 HEAP에 관련된 내용입니다.

  • 정적 메모리 할당: 프로그램 컴파일 시 결정된 크기만큼 메모리가 할당되고 프로그램이 종료될 때까지 해제되지 않습니다. 주로 데이터 세그먼트(Data Segment)에서 관리됩니다.
  • 동적 메모리 할당: 프로그램 실행 중에 필요에 따라 메모리를 할당하고 해제할 수 있는 방식이며 메모리의 크기와 생애 주기를 유연하게 조절할 수 있습니다. 힙 세그먼트(Heap Segment)에서 관리됩니다.

정적, 동적 메모리의 비교

 즉, 동적 메모리는 프로그램이 실행되는 중에 프로그래머의 설정, 관리에 의해 할당 값 등이 유동적으로 바뀌는 것을 뜻합니다. RPG게임에서 장착 아이템의 장착, 해제 상태를 다룰 때 사용할 수 있습니다. 동적 메모리 할당에 사용하는 주요 함수를 알아보겠습니다.

  • malloc: 요청한 크기만큼의 메모리를 할당하고, 해당 메모리 블록의 시작 주소를 반환합니다. 할당된 메모리는 초기화되지 않습니다.
  • calloc: 요청한 개수와 크기만큼의 메모리를 할당하고, 모든 메모리 블록을 0으로 초기화합니다.
  • realloc: 이미 할당된 메모리 블록의 크기를 변경합니다. 새 크기만큼 메모리를 재배치합니다.
  • free: malloc, calloc, realloc으로 할당된 메모리를 해제합니다. (해제 작업을 해야 메모리 누수 방지가 가능합니다.)

 free문의 메모리 누수 방지란, 만약 free문을 통해 메모리 해제를 하지 않으면 프로그램이 실행 도중 사용이 끝난 동적 메모리가 계속 RAM에 남아 있는 상태를 메모리 누수라고 표현하는데 프로그램의 메모리 사용량이 계속 증가되다가 메모리 고갈 -> 프로그램 크래시 상황으로 연결되어 프로그램이 강제 종료되거나 깨지는 현상이 발생할 수 있습니다. 추가로 성능 또한 저하되며 다른 작업이나 프로그램에 영향을 줄 수도 있습니다.
 
이제 기본적으로 다루게 될 개념들은 다뤘으니, 코드를 보겠습니다.

#include <stdio.h>
#include <malloc.h>
// h: 헤더파일, 함수 선언
// c: 함수 정의

//매크로 함수(Macro Function)
//#define SAFE_FREE(p) if(p) { free(p); p = NULL;} //p가 NULL이 아니면 
#define SAFE_FREE(p) \
do { \
    if (p) { \
        free(p); \
        p = NULL; \
    } \
} while (0)				//SAFE_FREE(p)가 하나의 문장으로 처리되 if문과 함께
//free() 함수는 동적 할당 메모리를 해제할 때 사용함. malloc, calloc, realloc 등의 함수로 \
할당된 메모리를 해제할 때 free 함수를 호출하여 메모리 누수를 방지

void main()
{
	//메모리 할당(Memory Allocate)
	//지금까지는 정적 메모리 할당에 대해서만 다뤘음.
	//오늘은 Dynamic Memory Allocate (동적 메모리 할당)

	//ROM: Read Only Memory		//컴퓨터 부품 
	//RAM: Random Access Memory	//컴퓨터 부품 전원 꺼지면 삭제 (휘발성)
	//RAM 내에서 3가지로 분류
	//DATA: 전역변수
	//STACK: 지역변수
	//HEAP: 사용자가 직접 관리

	//Stack Overflow: Stack 영역을 넘는 값

	//int overflow[2048*2048]; Stack 범위를 넘어서 오류 발생

	int* pVal = (int*)malloc(sizeof(int));	//4byte라 써도 되는데 int의 크기값 (메모리 할당)
	//void v;		//void는 고정된 값이라 void v는 변수할 수 없음. (변수 타입으로는 못씀)

	//void* pV;		//포인터는 8byte 고정으로 읽는 방법은 모르더라도 가능함.
					//(어떤 타입의 데이터를 가리킬 수 있는 포인터)
	//int value = 10;
	//pV = &value;	//int형 데이터의 주소를 void* 포인터에 저장

	//*pVal = 10;	//할당된 값이 NULL포인터일 수 있어서 초록불 뜸
	//메모리 할당 실패, 적중 실패
	if(pVal != NULL)
	{
		*pVal = 10;
		printf("*pVal: %d\n", *pVal);
		free(pVal);
		pVal = NULL;
	}

	//printf("pVal_: %d\n", *pVal);
	//free(pVal);
	//메모리 해제를 안해주면 그게 누적되면서 메모리를 계속 채우고 있음.
	//이를 메모리 누수(Memory Leak)라고 표현
	//해제는 꼭 해야한다. 해제한 후는 값은 추가로 넣으면 안됨.

	//int val = 7;
	//pVal = &val;
	if (pVal != NULL)	//패키지 이미 NULL이 제외되어있음.
	{
		free(pVal);		//free에 NULL이 제외되어있음.
		pVal = NULL;
	}

	if (5 > 3)
		SAFE_FREE(pVal);
	else
		printf("Failed!\n");

	//////////////////////////////////////////////////////////////////////

	//Dynamic Array (동적 배열은 순간의 크기가 결정되기 때문에 변수를 해도 됨.
	int pArrLen = 3;
	double* pArr = (double*)malloc(sizeof(double) * 3);
	if (pArr)
	{
		pArr[0] = 1.1;
		*(pArr + 1) = 2.2;
		*(pArr + 2) = 3.3;

		for (int i = 0; i < 3; ++i)
			printf("*(pArr + %d): %lf\n", i, *(pArr + i));

		//SAFE_FREE(pArr);
		if (pArr != NULL)
		{
			free(pArr);
			pArr = NULL;
		}
	}
}

 

<매크로 함수> (Macro Function)

매크로 함수는 특정 코드의 반복을 줄이고 특정 기능을 효율적으로 구현하기 위해 사용됩니다.
1. // #define SAFE_FREE(p) if(p) { free(p); p = NULL;}       //SAFE_FREE(p)는 if(p) {free(p); p = NULL;}과 같다.
2. // #define SAFE_FREE(p) \                                             //SAFE_FREE(p)는 do { ... } while(0) 과 같다.
  do { \ 
      if (p) { \
          free(p); \
          p = NULL; \
      } \
  } while (0)
1번과 2번 모두 같은 기능을 합니다. 차이는 do while문 존재 유무입니다. 둘 중 더 안전한 매크로는 2번입니다.
if(tmp)
  SAFE_FREE(p);
else
  printf("tmp");
라는 코드가 존재합니다. 여기서 SAFE_FREE(p) 매크로가 적용이되면,
if(tmp) 
  if(p) {free(p); p = NULL;}
else
  printf("tmp");
로 표현됩니다. 이렇게 되면 else문이 첫 번째, 두 번째 줄 if문이 중복되며 어느 쪽 if에 할당되어야할지 몰라 오류가 발생할 수 있습니다. 그러나, 2번 매크로를 사용하면,
if(tmp) 
{
  do
  {
     if(p)
     {
       free(p);
       p = NULL;
     }
  } while (0);
}
else
  printf("tmp");
형태로 if와 else간의 관계가 문제 없이 발동되어 더욱 안전한 코드가 완성됩니다.

 

'프로그래밍 C언어' 카테고리의 다른 글

구조체 (Structure)  (0) 2024.08.20
비트 단위 연산자 (Bitwise Operators)  (0) 2024.08.18
문자열 (String)  (0) 2024.08.16
함수 (Function)  (0) 2024.08.14
포인터 (pointer)  (0) 2024.08.13