앞서 포인터에 대해서 여러 가지를 알아보았습니다. 포인터란 무엇인지, 포인터와 배열과의 관계, 포인터와 함수와의 관계까지 포인터의 기초에 대해서 하나씩 살펴보았습니다.
이번 포스팅에서는 실제 실무에서 가장 많이 사용하는 방법 중에 하나인 포인터를 이용한 메모리 동적 할당에 대해서 알아보도록 하겠습니다.
메모리 할당이란 (Memory Allocation)
C언어와 JAVA는 고급언어지만 JAVA보다는 C언어가 조금 더 날것(?)에 가까운 언어입니다. 그래서 자동으로 해주기보다는 프로그래머가 직접 정의하고 구현하는 언어라 할 수 있습니다. 지금 말할 메모리 할당도 마찬가지입니다.
메모리 할당이란 프로그래을 만들면서 사용하고자 하는 메모리의 크기만큼 직접 할당받아서 사용하는 것을 말합니다. 물론 해제도 직접 해줘야 합니다. 해제하는 작업을 해주지 않으면 메모리 누수가 발생하여 프로그램에 문제가 발생할 수 있습니다.
다시 메모리 할당으로 돌아와서 메모리가 2byte가 필요하면 2byte를, 100byte가 필요하다면 100byte를 할당받아서 사용할 수 있습니다. 즉, 원하는 만큼 할당받아서 사용이 가능한 것이 이 포스팅에서 말씀드릴 메모리 동적 할당입니다.
메모리 할당 방법
메모리 할당에 대해서 알아보았으니 이제 할당받는 방법을 알아보겠습니다. 메모리를 할당하는 방법 역시 함수가 존재합니다. 그래서 우리는 함수 사용법만 알면 됩니다. 함수에는 3가지가 있습니다. malloc(), calloc(), realloc()입니다. 각각은 어떻게 사용하는지 보겠습니다.
malloc()
malloc() 함수는 memory allocation 의 약자로 만든 함수이며 사용법은 다음과 같습니다.
(void* ) malloc ( size_t size );
malloc() 함수를 사용하면 먼저 반환되는 값은 포인터입니다. 이 포인터는 void* 로 나타냈지만 int*, char* 등이 될 수도 있습니다. 이는 할당받은 포인터 변수에 맞게 사용해 주시면 됩니다.
size_t는 할당받을 크기를 의미합니다. 이 크기는 사용하고자 하는 값을 계산해서 넣어주시면 됩니다. 실 사용법은 예제를 통해서 알아보겠습니다.
소스코드 1 malloc.c
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수를 사용하기 위한 헤더파일
int main(int argc, char* argv)
{
int* iptr;
int n = 5;
iptr = (int* )malloc(n * sizeof(int)); // 메모리 할당
if (iptr == NULL)
{
printf("Memory allocation error.\n");
exit(0);
}
printf("포인터변수 크기 : %d\n", sizeof(iptr));
printf("메모리 크기 : %d\n", _msize(iptr));
free(iptr);
return 0;
}
결과 1
포인터 변수 크기 : 4
메모리 크기 : 20
malloc() 함수를 사용하기 위해서는 stdlib.h라는 헤더 파일이 필요합니다. 그래서 상단에 선언해 주었습니다.
상단에 선언한 변수는 할당받은 메모리 주소를 가질 포인터 변수를 선언합니다. 그리고 malloc() 함수를 이용해서 메모리를 할당받도록 합니다. 이때 크기는 n * sizeof(int)를 이용했습니다. sizeof() 함수는 parameter의 크기를 알려주는 함수입니다. 여기서는 int의 크기를 반환하므로 4가 됩니다. 여기에 5를 곱해서 20byte가 할당되는 것입니다.
화면에 값을 출력해보면 포인터 변수의 크기는 4byte, 메모리의 크기는 20byte라는 사실을 알 수 있습니다. 메모리의 크기를 알아보는 함수는 _msize() 함수입니다. parameter로 포인터를 넣어줍니다.
calloc()
메모리를 할당받는 함수중 두 번째는 calloc()입니다. Contiguous allocation의 약자입니다. 사용법은 아래와 같습니다.
(void* ) calloc ( size_t n, size_t size );
malloc() 함수를 잘 이해하셨다면 calloc() 함수는 이해하기 쉽습니다. malloc() 함수의 예제(소스코드 1)에서 크기를 선언할 때, n * sizeof()를 사용했어요. calloc() 함수에서는 각각을 분리해서 parameter로 넣어주시면 됩니다. 동일한 예제로 보면 iptr = (int *) calloc(5, sizoef(int));처럼 말이죠.
그럼 malloc() 함수와 calloc() 함수의 차이는 무엇일까요?
malloc() 함수는 메모리 공간만 할당해서 주고 calloc() 함수는 내부 값을 0으로 초기화해서 넘겨줍니다. 즉, malloc()으로 할당받은 메모리는 기존에 사용했던 값이 남아있어서 쓰레기 값이 존재할 수 있습니다. 하지만 calloc() 함수는 0으로 초기화돼서 나오기 때문에 아무것도 없는 빈 값이 나오게 됩니다.
그럼 소스코드 1과 같은 예제를 calloc() 함수로 변경해서 사용해 보겠습니다.
소스코드 2 calloc.c
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수를 사용하기 위한 헤더파일
int main(int argc, char* argv)
{
int* iptr;
int n = 5;
iptr = (int*)calloc(n, sizeof(int)); // 메모리 할당
if (iptr == NULL)
{
printf("Memory allocation error.\n");
exit(0);
}
printf("포인터변수 크기 : %d\n", sizeof(iptr));
printf("메모리 크기 : %d\n", _msize(iptr));
free(iptr);
return 0;
}
결과 2
포인터 변수 크기 : 4
메모리 크기 : 20
소스코드 2는 소스코드 1과 동일하며 malloc() 대신에 calloc() 함수로 수정했습니다. 따라서 parameter도 2개로 변경해서 넣어주었습니다.
realloc()
메모리를 할당받는 함수중 세 번째는 realloc() 함수입니다. 이 함수는 기존에 할당받은 메모리를 재할당할 수 있습니다. 즉, 메모리를 사용하다 보면 다 사용한 메모리의 일정 부분을 반환하거나 추가적으로 더 할당받을 필요가 있습니다. 이때는 메모리를 해제하고 다시 할당받는 것이 아닌 realloc() 함수를 사용해서 자동으로 할당받은 메모리를 변경할 수 있습니다.
realloc() 함수의 사용법은 다음과 같습니다.
(void* ) realloc((void *) pointer, size_t size);
realloc() 함수는 malloc(), calloc() 함수와 달리 parameter로 pointer 변수가 들어갑니다. 이때 포인터 변수는 기존에 메모리를 할당받아 시작 주소를 저장한 포인터가 됩니다. 즉 malloc()으로 할당받거나 calloc()으로 할당받은 포인터 변수를 넣어주시면 됩니다.
예제를 통해서 다시 한번 알아보도록 하겠습니다.
소스코드 3 realloc.c
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수를 사용하기 위한 헤더파일
int main(int argc, char* argv)
{
int* iptr;
int n = 5;
iptr = (int*)calloc(n, sizeof(int)); // 메모리 할당
if (iptr == NULL)
{
printf("Memory allocation error.\n");
exit(0);
}
printf("메모리 크기 : %d\n", _msize(iptr));
iptr = realloc(iptr, 10 * sizeof(int)); // 메모리 재할당
if (iptr == NULL)
{
printf("Memory allocation error.\n");
exit(0);
}
printf("메모리 크기 : %d\n", _msize(iptr));
free(iptr);
return 0;
}
결과 3
메모리 크기 : 20
메모리 크기 : 40
소스코드 2에서 사용한 코드입니다. calloc() 함수를 이용해서 메모리를 할당받았고 메모리의 크기가 20이라는 것도 출력하여 확인했습니다.
이후 realloc() 함수를 사용해서 기존에 할당받아 저장한 포인터 변수 iptr을 넣어주고 할당받을 메모리의 크기를 40으로 늘렸습니다. 그리고 반환되는 메모리의 주소는 다시 iptr이 받도록 하였습니다.
출력되는 결과물을 보면 처음에는 20byte 크기를 할당받았지만 두 번째는 40byte 크기를 할당받게 되었습니다. 그럼 다음에는 메모리를 할당받는 것만큼 중요한 메모리 해제에 대해서 알아보겠습니다.
메모리 해제
위에서 언급한 것처럼 메모리 할당은 사용하고 싶은 만큼 프로그래머가 직접 할당받기 때문에 메모리의 낭비가 사라지게 됩니다. 하지만 계속해서 메모리를 할당만 받게 되면 사용한 이후 더 이상 사용하지 않는 메모리들이 늘어나게 되고 낭비가 나타납니다.
아무리 작은 프로그램이라도 과학 메모리를 잡아먹게 되죠. 이에 반드시 해주어야 할 것이 메모리 해제입니다. 메모리를 해제하는 방법은 free() 함수입니다. 소스코드 1, 2, 3에서도 잠깐 언급이 되었지만 정확하게 알아보도록 하겠습니다.
free()
메모리를 해제하기 위해서는 free() 함수를 사용합니다. free() 함수는 아래와 같이 사용합니다.
free( (void* ) poitner );
free() 함수의 parameter는 메모리를 할당받은 포인터 변수입니다. malloc(), calloc()으로 할당받은 포인터 변수를 넣어주면 사용한 메모리가 반환됩니다.
Tip!
보안코드상 free() 함수를 이용해서 pointer의 메모리를 해제했다면 반드시 NULL 체크를 하고 NULL이 아니라면 NULL로 값을 바꿔주는 것이 안전합니다.
오늘 포스팅에서는 동적 메모리 할당과 메모리 해제에 대해서 알아보았습니다. 메모리의 할당과 해제는 C언어의 가장 큰 특징이라 할 수 있습니다. 그리고 반드시 사용해야 하는 부분이기도 하죠. 메모리는 Stack 영역과 Heap 영역이 있고 동적 메모리 할당은 Heap 영역을 사용합니다. 이 부분에 대해서는 추후 다른 포스팅에서 알아보도록 하겠습니다.
오늘 함께 알아본 메모리 할당은 실무에서도 가장 많이 사용하는 부분이니 반복해서 익숙해지시면 도움이 되실 겁니다.
'쿤즈 Dev > C' 카테고리의 다른 글
[C언어] 구조체(structures)란 무엇인가 (0) | 2021.03.17 |
---|---|
[C언어] 문자열과 배열 사이의 관계 (0) | 2020.12.21 |
[C언어] 포인터를 이용한 함수 사용 방법 (0) | 2020.12.16 |
[C언어] 포인터를 이용해서 배열 사용하기 (2) | 2020.11.10 |
[C언어] C언어의 꽃. 포인터를 알아보자! (0) | 2020.10.31 |
댓글