말감로그

[C언어] 동적 메모리 할당(Dynamic Memory Allocation) 본문

이론/자료구조

[C언어] 동적 메모리 할당(Dynamic Memory Allocation)

habbn 2024. 2. 2. 00:17
728x90

동적 메모리 할당이란?

 

- 컴퓨터 프로그래밍에서 실행 중(런타임)에 사용할 메모리 공간을 할당하는 것을 의미한다.

 

- 프로그램이 실행되기 전, 컴파일 시점에 소스 코드를 읽고 메모리 공간을 확보하는 것을

   정적할당 이라고 한다.

 

- 컴파일 타임이 아닌 프로그램이 실행되는 중인 런타임에 필요한 만큼의 메모리 공간을 확보하는 것을

   동적할당 이라고 한다.

 

동적 할당이 필요한 이유?

그때 그때 필요할 때마다 새로운 메모리 공간을 할당하는 것이 아니라,

컴파일 타임에 미리 넉넉한 메모리 공간을 확보해두면 되지 않을까??

 

가능하다.

 

하지만 메모리란 무한한 자원이 아니라 한정되어있다.

만약 우리가 1000000byte 사이즈의 메모리를 할당해두고 실제로는 10 byte만 사용한다면, 남은 메모리 공간을 비효율적으로 사용한다.

 

사용자의 입력을 받아 저장하는 경우처럼 프로그램의 실행 시점에 데이터의 크기를 알 수 있는 상황이라면, 런타임에 동적으로 메모리를 할당하는 것이 더 적절할 것이다! 

 

동적 할당이 필요한 이유는

그때 그때 필요한 만큼만 메모리 공간을 확보하고 ,

다 사용했다면 free 시켜줌으로써 (메모리 공간을 해제함으로서) 한정된 메모리 공간을 효율적으로 사용

할 수 있게 된다

 

동적 할당은 힙 영역에 필요할 때마다 메모리 공간을 할당하고, 더 이상 필요하지 않을 경우 메모리를 해제해주는 과정을 의미한다.

(함수가 종료되거나 변수의 영역을 벗어나면 자동으로 메모리 해제가 이루어지는 스택과는 다르다)

 

✔️ 힙(heap) 영역
- 사용자가 직접 관리할 수 있는 '그리고 해야만 하는' 메모리 영역.
- 힙 영역은 사용자에 의해 메모리 공간이 동적으로 할당되고 해제된다. 
https://tcpschool.com/c/c_memory_structure

 

  • 장점
    • 상황에 따라 원하는 크기 만큼의 메모리가 할당되므로 경제적이다. (malloc / calloc)
    • 이미 할당된 메모리라도 언제든 크기를 조정할 수 있다. (realloc)
  • 단점
    • c언어의 경우 GC(Garbage Collector - 필요 없는 메모리 수집하여 해당 공간의 할당 해제) 가 없기 때문에, 개발자가 직접 명시적으로 메모리를 해제해주어야 한다. (free)
    • 만약 이를 하지 않았을 경우, 메모리 누수가 나타나고 이는 디버깅 하기 매우 까다롭다.

 

메모리 할당

 

malloc 또는 calloc 을 호출하게 되면 힙 영역에 필요한 만큼의 메모리 공간을 확보하고 이후 반환 타입으로 해당 메모리 공간의 시작 위치를 포인터로 반환한다.

 

또한 할당된 메모리를 어떤 목적에 사용할 지 함수에서 판단하기 어렵기 때문에 void * 형을 return하며, 반환받는 쪽에서 타입 캐스팅을 통해 사용해야 한다.

(malloc은 단순히 메모리만 할당하는 함수, 어떠한 데이터형을 저장하는지 예측 할 수 없음)

 

1. malloc 함수

#include <stdlib.h>    //malloc 함수가 포함된 헤더 파일

void* malloc(size_t size); //malloc 함수의 원형

// Returns pointer to allocated block if OK
// NULL on Error

 

malloc 함수가 원하는 메모리 공간을 할당하지 못하는 경우(메모리가 부족한 경우)에는

NULL 포인터를 리턴한다.

 

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

int main() {
	int arr_1[5];	// 배열 선언
	int *arr_2;		// 포인터 변수 선언

	for(int i = 0; i < 5; i++) {
		arr_1[i] = i+1;	// 배열에 값 대입
	}

	arr_2 = (int*) malloc(sizeof(int)*5);	// 메모리 할당, 배열의 크기만큼 할당하기 위해 5를 곱함

	for(int i = 0; i < 5; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	return 0;
}

 

 

2. calloc 함수

#include <stdlib.h>

void* calloc(size_t elt_count, size_t elt_size)	// calloc 함수 원형

 

elt_count 는 할당할 요소의 개수이고, elt_size 는 각 요소의 크기

 

malloc과 calloc의 차이

int *arr=(int *)malloc(10*sizeof(int));
int *arr=(int *)calloc(10,sizeof(int));

 

malloc은 매개변수로 입력한 크기만큼을 그대로 할당하는 것이고,

calloc은 두 번째 매개변수의 크기를 첫 번째 매개변수 갯수 만큼을 할당해달라는 식으로 요청한다. 

두 명령어 모두 똑같은 크기의 공간을 확보한다.

 

malloc의 경우에는 메모리 공간만 할당하므로, 초기화되지 않은 메모리 공간에는 쓰레기값들이 들어있다.

calloc의 경우에는 할당 후 메모리 공간을 0으로 초기화한다.

-> 성능 자체는 초기화시키지 않는 malloc이 더 나으나,  상황에 따라 초기화가 필요한 경우는 calloc 선택!

 

 

메모리 해제

malloc 또는 calloc을 이용해서 메모리 공간을 할당하였고, 원하는 동작들을 모두 수행했다면 할당한 메모리 공간을 해제해주어야 한다.

 

free 함수 

 

- 힙 영역에 할당된 메모리를 해제하는 함수

 

- 동적으로 생성한 메모리 공간은 프로그램이 종료되도 자동으로 해제 되지 않는다. 그래서 수동으로 해제 해야 된다.

만약 해제를 해주지 않으면 필요하지 않은 메모리를 계속 점유하게 되므로 메모리 낭비가 된다.

(메모리 누수 - 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상 )

#include <stdlib.h>

void free(void* ptr)	// free 함수의 원형

 

ptr은 이전에 할당한 메모리를 가리키는 첫번째 주소값이다.

 

 

메모리 재할당

이미 할당되어 있는 메모리 공간의 크기를 변경하여 재할당 하는 것을 의미한다.

 

realloc 함수 

 

- 이미 할당된 공간의 크기를 바꿀 때 사용한다.

#include <stdlib.h>

void* realloc(void* memblock, size_t size);	// realloc 함수의 원형

 

이미 할당한 포인터 변수를 memblock에 넣고, 바꾸고 싶은 공간의 크기를 size에 입력하여 사용한다.

 

사용 예제

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

int main() {
	int arr_1[10];	// 배열 선언
	int *arr_2;		// 포인터 변수 선언
	int i;

	for(i = 0; i < 10; i++) {
		arr_1[i] = i+1;	// 배열에 값 대입
	}

	arr_2 = (int*) malloc(sizeof(int)*5);	// 메모리 할당, 배열의 크기만큼 할당하기 위해 5를 곱함

	for(i = 0; i < 5; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	printf("\n");

	// sizeof(int) = 4바이트
	realloc(arr_2, sizeof(int)*10);	// arr_2의 메모리를 40바이트로 재 할당
	// arr_2의 메모리 크기 : 20바이트 -> 40바이트

	for(i = 0; i < 10; i++) {
		arr_2[i] = arr_1[i];
		printf("%d ", arr_2[i]);
	}

	free(arr_2);	// free함수를 이용하여 메모리 해제

	return 0;
}

 

 

메모리 누수

1. 동적 메모리 할당 후 해제하지 않음

  •  malloc, calloc 등을 사용하여 메모리를 동적으로 할당하고 free (해제) 하지 않으면 할당된 메모리가 반환되지 않아 메모리는 누수가 발생한다.

2. 잘못된 시점에서 메모리 해제

  •  함수 내에서 메모리를 할당하고 함수가 종료될 때 해제하지 않으면, 해당 메모리는 누수가 발생한다.

3. 반복적인 할당과 해제

  • 반복적으로 메모리를 할당하고 해제하는 작업이 필요한 경우, 매번 할당한 메모리를 해제하지 않으면 누적된 메모리 누수가 발생한다.

-> 오류 방지 방법

 

1. 할당과 해제의 균형 유지

  • 동적으로 메모리를 할당한 후 반드시 메모리를 더 이상 사용하지 않을 때 free함수로 해제한다. 할당과 해제를 균형 있게 유지하여 메모리 누수를 방지한다.

2. 할당된 메모리의 라이프사이클 관리

  • 함수나 블록의 범위 내에서 할당한 메모리는 해당 범위를 벗어나기 전에 해제해야 한다.

3. 메모리 할당과 해제 관련 함수 활용

  • malloc대신 calloc을 사용하거나, 문자열 복사 함수로 strdup을 활용하여 문자열 메모리를 자동으로 할당 및 해제 할 수 있다.
  • strdup 함수는 문자열을 복사하고 복사된 문자열을 저장할 메모리를 자동으로 할당합니다. 이렇게 할당된 메모리는 이후에 더 이상 필요하지 않을 때 자동으로 해제되어 메모리 누수를 방지합니다

4. 디버깅 도구 활용

  • 메모리 누수를 탐지하기 위해 리눅수에서는 Valgrind와 같은 디버깅 도구를 활용할 수 있다.

 

 

 

참고

https://coding-factory.tistory.com/671

https://velog.io/@saint6839/C%EC%96%B8%EC%96%B4-%EB%8F%99%EC%A0%81-%EB%A9%94%EB%AA%A8%EB%A6%AC-%ED%95%A0%EB%8B%B9-%EA%B0%9C%EB%85%90-%EC%9E%A1%EA%B8%B0

https://dsnight.tistory.com/51

728x90

'이론 > 자료구조' 카테고리의 다른 글

DFS,BFS, 다익스트라, 플로이드 와샬  (0) 2024.02.07
위상정렬  (0) 2024.02.07
그래프  (1) 2024.02.07
복잡도(Big-O ,시간, 공간)  (1) 2024.02.07
Red-BlackTree  (0) 2024.02.03