프로그래밍 언어/C

정적/동적 메모리 할당(Memory Allocate)

gcreators 2024. 8. 16. 17:26

우리가 변수를 선언하면

컴퓨터는 메모리를 할당해주고 변수를 통해 그 메모리에 접근 할 수 있게 해줍니다.

 

컴퓨터 메모리는

ROM(Read Only Memory) / REM(Random Access Memory)으로 나뉘어지는데

 

ROM은 우리가 흔히 말하는 하드디스크이고

REM은 흔히 말하는 REM(렘)입니다

 

ROM은 저장공간 이라고 생각하면 편하고

REM은 어떤 주소를 찾아가도 속도가 같기에 프로그램을 불러들여서 사용과 반환을 하는 장치라고 보면 됩니다.

 

REM의 구조는

다음과 같은 구조를 가집니다

DATA 영역은 전역변수가 저장되고

STACK 영역은 지역변수가 저장되며

그리고 HEAP 영역은 우리 사용자(개발자)가 직접 관리를 할 수 있는 메모리 공간 입니다.

 

 

 

메모리 할당에는

정적, 동적 두가지 종류가 있는데

 

전역변수 및 지역변수는 정적 메모리 할당

그리고 HEAP 영역에 할당하는 메모리를 동적 메모리 할당(Dynamic Memory Allocate) 이라고 합니다

 

전역변수는 프로그램이 시작되었을때 할당되고 프로그램이 끝날때 반환되고

지역변수는 지역 { }내에서 변수가 선언 되었을 때 할당되고 지역을 벗어나면 반환이 자동적으로 이루어집니다.

다음 그림은

int b, int b2의 경우 지역변수로서 메모리 공간에 저장되지만 이후 지역이 끝나 반환되므로 실제로

int a, c, d만이 메모리에 남은 상태 입니다.

 

※stack overflow

한 함수에서 너무 큰 지역 변수를 선언하거나 함수를 재귀적으로 무한정 호출하게 되면 stack overflow가 발생할 수 있습니다.

//stack overflow 예시
int overflow[2048 * 2048];

너무 큰 지역 변수가 생기지 않도록 코딩을 잘 해야합니다.

 

그리고 HEAP영역에 할당되는 동적 메모리 할당 같은 경우는

int* pVal = (int*)malloc(sizeof(int));

다음과 같이 선언되며

두개의 헤더를 요구하므로

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

이를 꼭 확인 해야하겠습니다.

 

malloc은 컴퓨터에 메모리가 필요하다는 요청을 하는 것 이며

전역변수 및 지역변수 같은 경우 자료형을 보고 컴퓨터가 판단해서 메모리 공간을 준비를 해주지만

malloc은 몇 바이트가 필요하니까 이를 컴퓨터에 직접 요청 하는 것 입니다.

int* pVal = (int*)malloc(4);
int* pVal = (int*)malloc(12);
int* pVal = (int*)malloc(20);

다음과 같이 정수를 요구하며 우리가 필요한 만큼의 메모리를 직접 입력해야합니다.

 

다만 이렇게 정수를 입력할 경우

int형과 같이 os나 cpu의 연산속도 등 여러 요소에 의해 그 용량이 변경이 되는 경우가 있으니 

 

int pArrLen = 3;
double* pArr = (double*)malloc(sizeof(double) * pArrLen);

다음과 같이 하여 용량이 변하더라도 내가 요구하는 메모리 공간만큼 받아낼 수 있도록

sizeof 및 변수를 이용해 공간을 할당 받는 것이 중요하겠습니다.

 

malloc에 대해 살펴보면

 

void* 로 선언이 되는 것을 확인 할 수 있습니다.

void 형은 함수를 선언할때 많이들 사용했지만

단독변수로는 만들 수가 없으며 포인트형은 가능합니다.

void v; // 사용불가
void* pV; // 사용가능

void형은 다른 자료형들 처럼 메모리를 읽는 방법이 없기에 단독으로 사용이 불가능 합니다.
void*는 주소를 저장하므로 8byte라는 용량만 있으면 되기에 사용이 가능합니다.
void형 포인터는 메모리에 저장되는 값을 보고 메모리를 읽습니다.

int pArrLen = 3;
double* pArr = (double*)malloc(sizeof(double) * pArrLen);

(double*) 로 형변환을 해주었으니 double형으로 이제 읽게 되는 것 이지요.

 

그런데 이렇게 동적으로 할당 받는 메모리는

자동적으로 반환이 이루어지지 않습니다.

int* pVal = (int*)malloc(sizeof(int));
{
*pVal = 10;
printf("*pVal:%d\n", *pVal);
}

이렇게만 놔두면 메모리 공간을 쭈우욱 차지해서 프로그램에 과부하를 주게 되죠.

이를 메모리 누수(Memory Leak) 라고 합니다

그래서 반환도 직접 해야하는데

 

free(pVal);

이렇게 하면 메모리를 반환 할 수 있습니다.

그런데 여기서 또 주의 할 점이

 

free(pVal);
free(pVal);

코드를 작성하다 메모리를 반환하지 않은 줄 알고

반환을 한번 더 할 때가 있습니다.

이러면 프로그램이 터집니다.

 

반환할 메모리가 없는데 다시 한번 반환하겠다고 하니 오류가 나오는 것 이지요

 

실수를 하지 않으면 문제가 없겠지만

사람이 코딩을 하는 것이다보니 실수는 있을 수 밖에 없습니다

 

int* pVal = (int*)malloc(sizeof(int));

int* pVal은 메모리를 가리키는 주소를 담고 있습니다

메모리는 반환하더라도 그 메모리가 있던 주소는 기억하는 것 입니다.

 

그렇다면 메모리를 반환할때

이 주소도 같이 없애버린다면 해결 될 것 입니다.

	if (pVal != NULL)
	{
		free(pVal);
		pVal = NULL;
	}//메모리 해제, 주소 초기화까지가 한 세트이다.

다음과 같이 pVal의 값을 NULL로 만들어버리면 주소를 호출하려고 해도 NULL이 호출되게 됩니다

NULL값도 값이 있는 것이라 주소 자체를 없애버리는 것은 아니지만

 

free에는 NULL에 대한 예외처리가 존재하므로

두번을 사용하더라도 주소가 호출될 일이 없습니다.

 

메모리 단편화(Memory Fragmentation)

RAM에서 메모리의 공간이 작은 조각으로 나뉘어져 사용가능한 메모리가 충분히 존재하지만 할당(사용)이 불가능한 상태를 보고 메모리 단편화가 발생했다고 합니다.

배열을 선언하다 보면 이 메모리 단편화 현상으로 인해 배열선언이 되지 않을 때가 있습니다.

 

매크로 함수(Macro Functions)

#define SAFE_FREE(p) do { if(p) { free(p); p = NULL; } } while(0)

동적 메모리 할당의 경우 굉장히 자주 사용 되기에

사용 할 때마다

	if (pVal != NULL)
	{
		free(pVal);
		pVal = NULL;
	}

해당 코드를 붙일 수는 없는 일 입니다

 

함수로 만들어도 되겠지만

함수로 만들경우 int형 포인터로 선언한 덕분에 이중 포인터도 사용 해야 할 정도로 복잡한 함수가 완성이 됩니다.

그래서 #define을 이용하여

매크로 함수를 만들어 사용 하면 간단하게 사용 할 수 있습니다.

 

#define SAFE_FREE(p) if(p) { free(p); p = NULL; }

void main()
{
	int* pVal = (int*)malloc(sizeof(int));
	SAFE_FREE(pVal);
}

그런데 이 매크로 함수를 사용할 때 주의점이 있는데 #define을 활용하면 컴파일 단계에서 치환이 된다는 것은 다들 아실겁니다

 

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

만약에 이런 형태의 코드를 작성해서 사용하게 된다면 바로 오류가 나버리는데 그 이유는

 

if (5 > 3)
	if(pVal) { free(pVal); pVal = NULL; } 
else
	printf("Failed!\n");

사실은 이렇게 동작되어

아래의 if가 else와 붙어버리기에 코드가 오류가 나는 것 입니다.

 

그러니 선언 할 때 부터

#define SAFE_FREE(p) do { if(p) { free(p); p = NULL; } } while(0)

do while문을 이용해 묶어버리면

무조건 1회는 실행되는 do에서 해당 코드가 실행이 되고

while문은 그 조건이 0 즉, 거짓이므로 실행되지 않으니

이런 형태로 매크로 함수를 짤 수 있다는 것을 기억하고 잘 활용해야 합니다.

 

동적 메모리 할당 활용

 

int pArrLen = 3;
double* pArr = (double*)malloc(sizeof(double) * pArrLen); //3개짜리 double 배열 = 24byte
//void형 동적 메모리 할당 후 (double*) 형변환, double형 포인터 pArr에 해당 주소를 담음

if (pArr)
{
	pArr[0] = 1.1;
	*(pArr + 1) = 2.2;//주소 연산
	*(pArr + 2) = 3.3;

	for (int i = 0; i < pArrLen; ++i)
	{
		printf("*(pArr + %d): %1f\n", i, *(pArr + i));
	}
}

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

 

pArr != NULL은 풀어서 설명하면 pArr 값이 NULL이 아닐때 실행되므로

pArr을 입력해줘도 if문이 실행됩니다.

이전의 코드에서 pArr의 주소가 NULL로 초기화 될 경우 이 코드는 실행되지 않게됩니다.

 

 

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

구조체(Structure)  (0) 2024.08.20
다차원 배열(Multi-Dimensional Array)  (0) 2024.08.19
문자열 함수 활용 feat)예외처리  (0) 2024.08.14
문자열(String)  (0) 2024.08.14
함수(Function)  (0) 2024.08.13