[synergy 요청] c언어 동적 할당

목록으로 돌아가기

01. 동적 할당이란 무엇인가요? 🤔

기존에 우리는 메모리 할당 방법 중 정적 메모리 할당을 사용했습니다. 정적 메모리 할당이란 프로그램이 실행하는 순간 프로그램이 사용할 메모리 크기를 고려하여 메모리의 할당이 이루어지는 방법입니다. 쉬운 예제로 이해하면 main 함수에서 int 타입의 a라는 변수를 선언하면 STACK 상에 4B의 공간을 할당받으며 main 함수가 종료되면 사라진다고 배웠던 것이 바로 정적 메모리 할당입니다. 그렇다면 그 정적이란 단어와 반대로 동적인 메모리 할당은 어떻게 이루어지는 것일까요?

동적 메모리 할당 또는 메모리 동적 할당은 컴퓨터 프로그래밍에서 실행 시간 동안 사용할 메모리 공간을 할당하는 것을 말합니다. 사용이 끝나면 운영체제가 쓸 수 있도록 반납하고 다음에 요구가 오면 재할당을 받을 수 있습니다. 정적 메모리 공간과는 다르게 C/C++의 경우 개발자가 명시적으로 해제하거나 프로세스가 종료하기 전까지는 메모리 공간이 계속 유지됩니다. 그리고 동적 메모리 할당의 경우 STACK 영역이 아닌 HEAP 영역 안에 할당받습니다.

HEAP 영역은 프로그래밍 환경에서 기본 자료형이 아닌 보다 큰 크기의 데이터를 담고자 할 때 동적으로 할당하는 메모리 공간입니다. 상황에 따라 원하는 크기만큼의 메모리가 할당되므로 효율적이라는 장점이 있지만 위에서 설명한 바와 같이 개발자가 명시적으로 해제해주지 않으면 프로세스가 종료할 때까지 메모리 공간을 차지하므로 메모리 누수가 일어날 수 있다는 단점이 있다.


02. 동적 할당의 종류는? 🤔

동적 할당과 관련된 함수는 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이 가리키는 메모리를 해제 (메모리 누수 조심!)

C++의 경우는 이 동적 할당이 new와 delete 함수로 제공된다.


03. 동적 메모리 할당 사용은 어떻게 하나요?

malloc 함수 예제를 사용하여 설명드리겠습니다. 해당 예제는 충남인력개발원 C언어(박정석 강사님) 수업에 사용되었던 예제입니다(제 Github 프로젝트 ‘LearningC/[C] 11 day/00_Examples에서 찾아보실 수 있습니다). 예제는 저희가 처음 배웠던 stack4에서 initStack 함수를 가지고 설명드리겠습니다. 현재 예제는 이후에 Struct (구조체) 설명에서 다시 또 사용됩니다.

’’’

// 구조체 생성
typedef struct {
	int *pArr;
	int size;
	int tos;
} Stack;

// 구조체 Stack 생성 함수 
void initStack(Stack* ps, int size);

// main 함수
int main() {
    Stack s1;
	initStack(&s1, 100);
    free(s1->pArr);
    return 0;
}

// initStack 함수
void initStack(Stack* ps, int size) {
   ps->pArr = malloc(sizeof(int) * size);
   assert(ps->pArr );
   ps->size = size;
   ps->tos = 0;
}

’’’

먼저 구조체에 대해 아신다는 가정하에 설명드리는 점 죄송합니다. 구조체에 대한 자세한 내용은 다시 설명드리겠습니다. 해당 예제는 Stack 방식으로 자료를 저장할 수 있도록 자료 구조를 연습하는 예제이지만 동적 메모리 할당 부분을 설명하기 위해 일부만 가져왔습니다.

현재 구조체의 모습을 이해하기 쉽게 그림으로 표현해보았습니다. pArr이라는 int 포인터와 int 타입의 size와 tos가 묶여 하나의 Stack이라는 구조체로 설정되어있습니다. 포인터에 대한 개념도 이해하셨다고 가정하고 진행합니다. 혹시 포인터 개념이 어려우시면 synergy 댓글에 포인터 개념도 요청해주세요!

포인터는 선언만 했을 때에는 위의 그림과 같이 아무것도 가르키지 않은 상태로 있습니다. 포인터는 무조건 대상을 가르키고 역참조해야 한다고 배웠죠? 그래서 현재 pArr은 역참조할 수 없는 상태입니다. 이렇게 먼저 간단한 Stack의 모습을 설명해드렸습니다. 그럼 이제 위의 코드를 한 줄씩 해석하며 설명드리겠습니다.

코드의 시작은 main 함수입니다.

’’’

Stack s1;
initStack(&s1, 100);

’’’

현재 코드까지의 그림을 STACK 공간의 그림으로 표현하면 다음과 같을 겁니다.

Stack 구조체 변수 s1을 선언하고 initStack 함수의 인자값으로 s1을 주소값으로 전달합니다. 또한 100이라는 정수값을 함께 보내는데 위에 선언된 함수 void initStack(Stack* ps, int size); 코드를 보면 100은 size 값으로 전달되었습니다. 함수의 인자값만 보자면 아마도 100만큼의 size 공간을 s1에 할당되게 하는 코드라는 것을 유추할 수 있습니다. initStack 함수가 호출되었으니 다음 코드 해석은 initStack 함수의 코드 첫 줄입니다.

’’’

void initStack(Stack* ps, int size) {
   ps->pArr = malloc(sizeof(int) * size);
   assert(ps->pArr );
   ps->size = size;
   ps->tos = 0;
}

’’’

드디어 우리가 보고 싶었던 malloc 함수가 여기서 등장합니다. 동적 메모리 할당의 정의와 같이 아마도 우리가 원했던 size 만큼의 공간을 할당해주는 것이라고 유추해보아야 합니다. 이해하기 쉽게 다시 그림을 이어서 그려 보여드리겠습니다.

이렇게 그림을 그려 쉽게 설명 드렸습니다. int 타입의 공간이 100만큼 할당되었음을 시각적으로 볼 수 있었습니다.

예제와 같이 Stack 구조체의 int * 타입 pArr 변수가 공간이 할당되었습니다. 가르키는 대상이 생겼으니 이제 역참조도 가능해졌겠죠? 해당 공간들을 접근할 때는 s1->pArr[0]과 같이 배열처럼 인덱스로 접근하여 값을 할당하거나 수정할 수 있게 됩니다. 우리는 int 타입의 공간을 100개 만들었으므로 s1->pArr[0]은 0번째 int 타입 변수입니다. 다양하게 활용될 수 있는 함수이므로 꼭 개념을 이해해주시기 바랍니다! 그리고! 꼭 free 함수로 메모리를 해제시켜 메모리 누수를 확인해야 한다는 점 기억해주시기 바랍니다.

여기까지 동적 메모리 할당 개념에 대해 설명해보았습니다. 설명이 틀린 부분이나 부족한 부분이 있다면 꼭 아래 댓글로 요청해주세요! 감사합니다. 😄

author-profile
Written by jonghoinside

댓글