동적 할당
개요
이번 시간에는 C 언어의 핵심 요소 중 하나인 메모리 관리와 동적 할당에 대해 집중적으로 살펴보겠다. 동적 할당(Dynamic Allocation)은 프로그램 실행(런타임) 도중 필요한 메모리 공간을 할당하는 과정으로, 메모리 자원이 제한적인 환경에서 특히 중요하다. C 언어는 malloc, calloc, realloc, free와 같은 함수를 통해 개발자가 메모리를 효율적으로 관리할 수 있도록 지원한다. 이러한 동적 할당 메커니즘을 통해, 개발자는 필요한 메모리만을 할당하고, 사용이 끝난 메모리를 해제함으로써 시스템의 메모리 사용을 최적화할 수 있다.
본문
사용 이유
동적 메모리 할당과 해제는 C언어에서 메모리 사용의 효율성을 높이는 중요한 역할을 한다. 정적 할당 방식에서는 프로그램이 시작될 때 모든 필요한 메모리를 할당하며, 프로그램이 종료될 때까지 이 메모리를 유지한다. 이는 필요 이상의 메모리 자원을 소비하게 되며, 특히 메모리 자원이 제한적인 환경에서는 자원의 낭비를 야기한다. 반면, 동적 할당은 필요한 메모리를 프로그램 실행 중 필요한 시점에 할당하고, 더 이상 필요하지 않을 때 해제함으로써 메모리를 보다 유연하고 효율적으로 관리할 수 있게 한다. 이러한 방식은 메모리 사용을 최적화하고, 제한된 환경에서의 메모리 관리에 있어 효과적인 방법이 된다.
#include <stdio.h>
#include <stdlib.h>
#define true 1
int main()
{
int staticArray[1000];
int* dynamicArray = (int*)malloc(1000 * sizeof(int));
if (dynamicArray == NULL)
return 1;
staticArray[0] = 1;
dynamicArray[0] = 1;
printf("staticArray[0] = %d\n", staticArray[0]);
printf("dynamicArray[0] = %d\n", dynamicArray[0]);
free(dynamicArray);
while (true)
{
}
return 0;
}
다음 코드에서는 staticArray는 정적으로 할당된 배열로, 컴파일 시간에 크기가 결정되며 프로그램 실행 동안 크기가 변하지 않는다. 이 배열은 프로그램 시작 시 1000개의 정수를 저장할 수 있는 공간을 메모리에 할당받고, 프로그램이 종료될 때까지 그 공간이 유지된다.
반면, dynamicArray는 동적으로 할당된 배열이다(할당하고 해제하는 방법은 후술하도록 하겠다). 이 배열은 프로그램 실행 중 필요에 따라 메모리를 할당받으며, 사용이 끝난 후에는 free 함수를 통해 메모리를 해제할 수 있다. 위 코드의 예에서, dynamicArray에 할당된 메모리는 free 함수를 호출함으로써 해제되며, 이후로는 해당 메모리 공간을 다른 용도로 사용할 수 있게 된다.
이 코드에 있는 while(true) 루프는 무한히 계속 실행된다. 이 루프가 실행되는 동안 staticArray는 계속해서 메모리를 차지하고 있지만, dynamicArray에 대한 메모리는 이미 해제되어 메모리를 사용하지 않고 있다. staticArray에 할당된 메모리는 프로그램이 종료될 때까지, 즉 main 함수가 리턴될 때까지 유지되며, 이는 코드에서 4000바이트(정수 하나당 4바이트로 가정)에 해당한다.
사용법 (1) - calloc
C 언어에서 동적 할당은 calloc과 malloc 두 가지 함수를 사용할 수 있다. 먼저 calloc은 지정된 수의 요소에 대해 메모리를 할당하고, 모든 비트를 0으로 초기화하는 동적 할당 방법이다. calloc을 사용하려면 다음과 같은 절차를 따르면 된다.
1. 포인터 선언: 먼저, 포인터를 선언하여 메모리를 할당할 때 사용할 포인터를 생성한다. 동적 할당된 메모리는 프로그램의 힙 영역에 위치하며, 이 영역의 메모리는 직접적인 이름이나 변수로 접근할 수 없다. 따라서 포인터를 이용하여 메모리의 주소를 저장하고, 이 주소를 통해 할당된 메모리에 접근하고 조작해야 한다.
int *ptr
2. 메모리 할당 및 초기화: calloc 함수는 메모리 할당과 동시에 초기화를 수행한다. 이 함수는 할당할 원소의 수와 각 원소의 크기를 인자로 받는다. calloc의 반환형은 void*이며, 이는 어떤 타입의 객체도 가리킬 수 있는 일반 포인터다. 이러한 설계는 calloc을 특정 타입에 구속되지 않고, 다양한 데이터 타입의 메모리 할당에 유연하게 사용할 수 있도록 한다. 따라서 앞선 포인터의 자료형을 int 형으로 선언하였기 때문에, 해당 자료형의 데이터를 다루기 위해 (int*) 형으로 형변환을 해주어야 한다.
ptr = (int*)calloc(1000, sizeof(int));
3. 포인터 사용: 할당된 메모리를 사용한다. calloc에 의해 할당된 메모리는 모두 0으로 초기화되었기 때문에, 초기화된 값을 확인하거나 새로운 값을 할당할 수 있다.
if(ptr != NULL)
{
ptr[0] = 10;
printf("First element: %d\n", ptr[0]);
}
4. 메모리 해제: 동적으로 할당된 메모리는 더 이상 필요하지 않을 때 free 함수를 통해 해제해야 한다. 이는 메모리 누수(leak, 프로그램이 동적으로 할당한 메모리를 해제하지 않아 발생하는 문제)를 방지하는 데 중요한 역할을 한다.
free(ptr);
사용법 (2) - malloc
malloc은 지정된 크기의 메모리를 할당하지만, calloc과 달리 할당된 메모리를 초기화하지 않는다. 이로 인해 메모리에는 임의의 데이터, 즉 쓰레기 값이 남아 있을 수 있지만 calloc에 비해 빠르다는 특징이 있다. malloc 또한 calloc과 같은 과정을 거쳐 사용할 수 있다.
1. 포인터 선언: 먼저, 포인터를 선언하여 메모리를 할당할 때 사용할 포인터를 생성한다.
int *ptr
2. 메모리 할당 및 초기화: malloc 함수는 할당할 메모리의 크기를 바이트 단위로 인자로 받는다.
ptr = (int*)malloc(1000 * sizeof(int));
3. 포인터 사용: 할당된 메모리를 사용한다. malloc에 의해 할당된 메모리는 초기화되지 않았으므로, 사용 전에 적절한 초기화가 필요할 수 있다.
if(ptr != NULL)
{
memset(ptr, 0, 1000 * sizeof(int)); // 메모리 초기화
ptr[0] = 10;
printf("First element: %d\n", ptr[0]);
}
4. 메모리 해제: 동적으로 할당된 메모리는 더 이상 필요하지 않을 때 free 함수를 통해 해제해야 한다. 이는 메모리 누수를 방지하는 데 중요한 역할을 한다.
free(ptr);
사용법 (3) - realloc
만약 이미 할당된 메모리 블록의 크기를 변경하고 싶으면 어떻게 해야 할까? c언어에서는 메모리의 크기를 확장하거나 축소할 때 realloc 함수를 사용한다. 즉, 새로운 메모리 영역을 할당하고 기존 데이터를 복사한 후, 기존 메모리를 해제한다. 이 과정에서 메모리 크기를 줄일 때, 초과하는 부분의 데이터는 손실될 수 있고, 메모리 크기를 확장할 때, 추가된 메모리 영역은 초기화되지 않는다.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = malloc(100 * sizeof(int));
if (ptr == NULL)
{
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
ptr = realloc(ptr, 200 * sizeof(int));
if (ptr == NULL)
{
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
free(ptr);
}
요약
동적 할당
1. 정의: 프로그램 실행(런타임) 도중 필요한 메모리 공간을 할당하는 과정
2. 과정
a. 포인터 초기화
b. 메모리 할당
c. 포인터 사용
d. 메모리 해제
3. 함수: malloc, calloc, realloc, free
'Language > C' 카테고리의 다른 글
[C] 구조체(Structure) (2) | 2024.01.31 |
---|---|
[C] 포인터(Pointer) (2) | 2024.01.22 |