[C언어] 동적 메모리 할당의 세가지 방법 malloc, calloc, realloc
동적 메모리 할당
우리는 이제껏 메모리를 할당할때 정적으로 할당했습니다. 어떤 것이냐면
int arr[100];
이렇게 할당을 했었죠. 뭐 문제없습니다. 실행도 잘 되구요.
하지만 이런 상황은 조금 불편할 수 있겠죠.
● 처음 int배열 100개가 필요하다고 생각했는데 프로그램을 실행하다 보니 int배열이 500개만 필요한 경우
● int배열 500개가 모자라 배열 500개를 메모리에 더 할당해야 할 경우
우리는 메모리를 효율적으로 사용하기 위해서 너무 남거나, 너무 모자란 메모리 할당을 피해야할텐데요. 그 목적을 달성하기 위해서 이런 코딩은 어떨까요?
#include <stdio.h> int main() { int size, i; scanf("%d", &size); int arr[size]; for (i = 0; i < size; i++) scanf("%d", &arr[i]); for (i = 0; i < size; i++) printf("arr[%d] = %d\n",i,arr[i]); }
size를 입력으로 받고, 배열의 크기를 size만큼 할당하는 겁니다. 그리고 그 size만큼 for루프를 돌아 정수를 입력받고 있네요.
이 코드는 분명 얼핏 보기에는 문제가 없어보입니다. 그러나 메모리에 할당하는 시점을 고려한다면 이 코드는 실행조차 안되고 컴파일 에러가 발생하게 됩니다.
왜요?
stack에서 메모리는 컴파일 시점에서 결정됩니다. 우리는 실행중에 입력을 받고, 실행 중에 메모리를 할당해야하는데, 그 앞의 단계인 컴파일단계에서는 얼마만큼의 메모리를 할당할지 알 수 있는 방법이 없죠. 컴파일러는 모릅니다.
우선 우리는 메모리 구조에 대해서 조금 알아야 할 필요가 있습니다.
스택영역
우리는 이제껏 스택영역에 메모리를 할당해 왔습니다. 컴파일 시점에 결정되는 영역입니다.
함수의 지역변수, 매개변수등이 이 메모리에 할당이 됩니다. 그리고 함수가 종료되었을때 할당된 메모리를 반환하게 됩니다.
그러니까 메인 함수안의 변수들은 모두 스택영역에 할당이 된거죠.
힙 영역
이 영역의 메모리는 실행시점(Run Time)에 결정됩니다. 프로그래머에 의해서요. 이 영역을 힙영역이라고 합니다.
한가지 더 보충설명을 하자면 스택영역은 높은 주소에서 낮은 주소로 할당이 되고, 힙영역은 낮은 주소에서 높은 주소로 할당이 됩니다. 그래서 재귀함수를 통해 함수를 계속호출하게 되면 힙영역을 침범해 스택오버플로우가 발생하게 됩니다. 그 반대의 경우는 힙오버플로우가 발생합니다.
데이터 영역
이 영역의 메모리는 정적변수, 전역변수, 구조체 등 함수 외부에서 선언되는 변수들이 이 메모리에 할당됩니다.
코드 영역
코드영역에는 프로그램의 실행 명령어들이 존재합니다.
우리가 이번에 주목해야할 영역은 힙영역입니다. 위의 코드를 정상적이게 동작시키기 위해서는요.
그 목적을 달성하기 위해서 나온 함수가 바로 malloc함수입니다. malloc함수는 stdlib헤더에 선언되어 있으며 malloc함수를 사용하기 위해서는 stdlib.h를 include해야합니다.
void *malloc(size_t size)
이 함수는 size만큼의 메모리를 힙영역에 할당합니다. 어떤 자료형일지 모르니 반환형 데이터는 void포인터입니다.
하지만 그냥 메모리만 할당하고 해제하지 않으면 메모리가 누출됩니다. 우리는 메모리를 이제 쓰지 않을 경우(거의 함수 반환 직전)에 free함수를 써서 메모리를 해제해야합니다.
void free(void *ptr)
이제 malloc함수와 free함수를 사용해서 위의 코드를 오류없이 실행시켜보도록 하지요.
#include <stdio.h> #include <stdlib.h> int main() { int size, i; scanf("%d", &size); int *arr=(int*)malloc(sizeof(int)*size); for (i = 0; i < size; i++) scanf("%d", &arr[i]); for (i = 0; i < size; i++) printf("arr[%d]=%d\n", i, arr[i]); free(arr); }
무리없이 실행도 되고, 원하는 결과를 얻을 수 있습니다.
malloc함수 외에도 calloc, realloc함수가 있습니다.
void *calloc(size_t n, size_t size)
calloc은 malloc과 힙영역에 할당하는 것을 똑같습니다. 사용법과 초기값이 다른데요.
calloc은 할당된 메모리를 전부 0으로 초기화합니다. malloc은 0으로 전부 초기화 시키지 않죠.
쓰임새는 아래의 코드와 같습니다.
#include <stdio.h> #include <stdlib.h> int main() { int n, i; scanf("%d", &n); int *arr=(int*)calloc(n,sizeof(int)); printf("calloc 0으로 초기화\n"); for (i = 0; i < n; i++) printf("arr[%d]=%d ", i, arr[i]); printf("\n"); for (i = 0; i < n; i++) scanf("%d", &arr[i]); for (i = 0; i < n; i++) printf("arr[%d]=%d\n", i, arr[i]); free(arr); }
realloc함수는 할당된 메모리를 다시 할당할때 쓰입니다. 기존에 할당했던 포인터와 다시 할당할 size를 매개변수로 전달합니다. 기존에 있던 값은 변함이 없습니다.
void *realloc(void *memblock, size_t size)
realloc은 아래처럼 쓰면 됩니다.
#include <stdio.h> #include <stdlib.h> int main() { int n,m,i; printf("처음 크기 입력\n"); scanf("%d", &n); int *arr=(int*)malloc(sizeof(int)*n); for (i = 0; i < n; i++) scanf("%d", &arr[i]); for (i = 0; i < n; i++) printf("arr[%d]=%d\n", i, arr[i]); printf("다시 할당될 크기 입력\n"); scanf("%d", &m); //realloc함수도 다시 할당 arr = (int*)realloc(arr, sizeof(int)*m); for (i = n; i < m;i++) scanf("%d", &arr[i]); for (i = 0; i < m; i++) printf("arr[%d]=%d\n", i, arr[i]); free(arr); }
2차원 배열은 어떻게 할당할까요?
우선 1차원 배열의 메모리를 힙에 할당하고, 1차원배열 각각 메모리를 할당하면 됩니다.
메모리 해제할때도 일차원배열 메모리를 해제하고, 2차원배열 메모리를 하제하면 됩니다. 조금 복잡할 수도 있어요.
#include <stdio.h> #include <stdlib.h> int main() { int n, m, i; int **arr; scanf("%d %d", &n,&m); arr = (int**)malloc(sizeof(int *)*n); for (i = 0; i<n; i++) arr[i] = (int *)malloc(sizeof(int)*m); for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) scanf("%d", &arr[i][j]); printf("\n\n"); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) printf("arr[%d][%d]=%d\n", i, j, arr[i][j]); printf("\n"); } for (i = 0; i<n; i++) free(arr[i]); free(arr); }
이제까지 메모리 할당 함수를 사용해서 동적메모리를 할당하는 방법을 알아보았습니다. 조금 어렵죠?