프로그래밍 언어/C언어

C언어 15. 메모리 동적 할당

닉네임못짓는사람 2020. 7. 20. 18:04
반응형

이번 글에선 C언어에서의 메모리 동적 할당에 대해서 알아보도록 하겠습니다.

지금까지 우리는 메모리 공간을 확보할 때 변수나 배열을 선언하는 방법을 사용했습니다.

이러한 방법의 특징은 코드를 작성하는 단계에서 프로그래머가 확보할 메모리 공간의 크기를

미리 예상하고 작성해야 한다는 점입니다. 이를 메모리 정적 할당이라고 합니다.

메모리의 낭비


하지만 다음과 같은 경우가 있다면 어떻게 될까요?

만약 5개의 속담을 사용자로부터 입력받아 배열에 저장하는 프로그램이 있다고 생각해봅시다.

이를 위해선 배열을 다음과 같이 선언할 수 있을 것입니다.

char pro[5][80];

속담의 정확한 길이를 모르기 때문에 이를 저장할 배열의 길이는 충분히 길게 선언해야 합니다.

하지만 이러한 방식은 필연적으로 메모리 공간의 낭비를 발생시킵니다.

각 배열의 요소는 80바이트만큼 메모리 공간을 확보하는데,

입력되는 문자열이 10바이트라면 나머지 공간은 전부 낭비되는 것이지요.

이러한 상황을 해결하기 위해서 프로그램이 실행되는 도중에 입력되는 데이터에 맞게

메모리 공간을 동적으로 할당하는 것을 메모리 동적 할당이라고 이야기합니다.

메모리 동적 할당 함수(malloc, calloc, realloc)


이런 메모리 동적 할당을 가능하게 해주는 함수는 주로 malloc을 사용하며, 함수 원형은 다음과 같습니다.

malloc을 사용하기 위해선 stdlib.h헤더 파일을 선언해주셔야 합니다.

void* malloc(unsigned int);

이 함수는 원하는 크기의 메모리 공간을 확보하고 이 공간의 포인터를 반환합니다.

이때 전달 인자는 언제나 양수이기 때문에 데이터 타입이 unsigned int입니다.

또한 반환하는 포인터가 어떤 용도로 사용될지 모르기 때문에 void형 포인터로 반환합니다.

이는 명시적 형 변환을 사용해 프로그래머가 원하는 타입의 포인터로 변경할 수 있고,

이 포인터를 포인터 변수에 저장하여 사용할 수 있습니다.

int* in_P = (int*)malloc(4);

위와 같이 코드를 작성하면 4바이트 크기의 메모리 공간을 확보한 뒤,

이 공간의 포인터를 int형 포인터로 변경해 포인터 변수 in_P에 저장할 수 있습니다.

이러한 동적 할당을 사용할 때 주의해야 할 점이 몇 가지 있는데, 한번 알아보도록 합시다.

 

먼저 동적 할당되는 메모리는 반납해야 한다는 점입니다.

하나의 프로그램은 실행 시에 일정한 메모리 영역을 사용합니다.

이 영역은 다시 몇 개의 구역으로 나뉘고 이를 기억 부류라고 이야기하는데,

주로 프로그램이 올라가는 코드 영역과 데이터가 저장되는 데이터 영역으로 구분됩니다.

 

또한 데이터 영역은 다시 자동 변수들이 할당되는 스택 영역과 동적 할당되는 기억공간인 힙 영역,

그 외에 외부 변수는 정적 변수를 위한 데이터 영역으로 구분됩니다.

 

여기서 힙에서 할당받은 기억공간은 자동 변수와 달리 생존기간이 프로그램이 끝날 때까지입니다.

이 공간은 특정 함수에 종속되지 않으며, 프로그램이 종료될 때까지 사용할 수 있습니다.

만약 특정 함수에서 자동 변수와 메모리 동적 할당을 사용한다고 생각해봅시다.

이 함수가 종료되면 해당 함수에서 선언된 자동 변수들의 메모리 공간은 자동적으로 반납됩니다.

 

하지만 동적 할당한 메모리 공간은 함수가 끝나 이를 다시 사용하든 말든 공간을 계속해서

차지하고 있게 되기 때문에, 메모리 공간의 낭비가 생길 수도 있습니다.

이러한 이유로 malloc을 사용해 힙 공간에서 공간을 확보하면 이를 다시 반납해주어야 합니다.

 

이런 메모리 공간을 반환하는 함수는 free함수가 있으며, 함수 원형은 다음과 같습니다.

void free(void*);

전달 인자는 void형 포인터인데, 반환할 메모리 공간이 어떤 형태로 사용되었어도

상관없게 하기 위해서 이와 같이 만들어졌습니다.

 

다음으로 주의해야 할 점은 동적 할당한 메모리 공간은 확인해야 한다는 점입니다.

이게 무슨 뜻이냐 하면, 메모리 동적 할당을 할 경우에 힙 공간에 충분한 공간이 존재하지 않으면

함수는 0번지의 포인터를 반환하게 되는데, 이를 널 포인터라고 말합니다.

이는 포인터의 특별한 상태를 나타내기 위해 사용되며 참조할 수 없는 포인터입니다.

이 널 포인터를 참조하게 되면 프로그램은 오류를 표시하며 중단됩니다.

따라서 프로그래머는 이러한 상황에 맞추어 malloc등의 함수가 널 포인터를 반환했을 때의 코드를

따로 작성해주셔야 합니다.

int *in_P;
in_P = (int)malloc(sizeof(int));
if (in_P == 0) {
	printf("메모리가 부족합니다.");
}

널 포인터는 보통 "NULL"이라는 이름으로 기호화하여 사용하는데,

전처리 단계에서 0으로 바뀌기 때문에 0과 같다고 생각하셔도 무방합니다.

 

그럼 이제 위에서 배운 내용으로 메모리를 동적으로 할당받아 문자열을 저장하는 코드를 작성해봅시다.

#include<stdio.h>

int main() {
	int i;
	char tmp[80];		//문자열의 길이를 계산하기 위한 임시배열
	char* pro[5];		//문자열을 저장할 포인터배열
	char ch;
	printf("입력할 문자열의 길이는 80글자 미만으로 해야 합니다.\n");
	for (i = 0; i < 5; i++) {
		printf("문자열을 입력하세요. : ");
		gets(tmp);
		pro[i] = (char*)malloc(strlen(tmp)+1);	//끝에 NULL을 넣기위해 1을 더함
		strcpy(pro[i], tmp);
	}

	for (i = 0; i < 5; i++) {
		printf("%s\n", pro[i]);
	}

	for (i = 0; i < 5; i++) {
		free(pro[i]);
	}
	
	return 0;
}

위의 코드는 문자열을 입력받아 그 길이만큼 메모리 공간을 확보하고, 포인터 배열에 저장합니다.

문자열의 길이를 확인하려면 먼저 문자열을 저장해야 하기 때문에 tmp배열을 임시 배열로 사용합니다.

주의점은 strlen은 문자열 끝의 NULL은 포함하지 않고 길이를 계산하기 때문에, 1을 더해주어야 합니다.

만약 1을 더하지 않고 코드를 실행하면 오류가 발생하며 정상적으로 실행되지 않을 것입니다.

 

동적 할당 함수에는 malloc외에도 calloc과 realloc이 존재합니다.

먼저 calloc은 메모리를 동적으로 할당받아 배열의 용도로 사용하고자 할 때 사용하면 편리합니다.

함수의 원형은 다음과 같습니다.

void* calloc(unsigned int, unsigned int);

반환값은 malloc과 동일하며, 두 번째 인자 또한 할당받고자 하는 메모리 공간의 크기를 입력합니다.

첫 번째 인자는 이 메모리 공간을 몇 개 할당받을지 입력합니다.

즉 int형 변수 5개로 사용하기 위해 메모리 동적 할당을 사용하면 다음과 같이 할 수 있습니다.

int* in_P;
in_P = (int*)calloc(5, sizeof(int));

malloc과 calloc의 공간 할당 방식에 기본적인 차이는 없습니다.

다만 calloc으로 메모리 공간을 할당받으면 값을 0으로 초기화시켜줍니다.

 

realloc함수는 이미 할당받은 메모리 공간의 크기를 조절할 수 있습니다.

함수 원형은 다음과 같습니다.

void* realloc(void*, unsigned int);

반환값은 지금까지와 동일하며, 두 번째 인자로 다시 확보할 기억공간의 크기를 입력합니다.

첫 번째 인자는 변경할 포인터 변수의 변수명입니다.

int* in_P;
in_P = (int*)calloc(5, sizeof(int));
in_P = (int*)realloc(in_P, 10 * sizeof(int));

새로 할당받는 메모리 공간의 위치는 기존에 할당받은 위치와 동일합니다.

때문에 재할당받는 공간의 크기가 기존보다 크면 데이터는 그대로 보존되지만,

크기가 작아지면 반대로 데이터 손실이 생기기 때문에, 이 점 주의해두시길 바랍니다.

또한 두 함수 사용시에도 메모리 공간을 다 사용한 뒤엔 free로 이를 반납해주셔야 합니다.

 

이 정도로 메모리 동적 할당에 대한 설명은 마치도록 하겠습니다.

다음 글에선 변수의 영역에 대해서 알아보도록 하겠습니다.

반응형

'프로그래밍 언어 > C언어' 카테고리의 다른 글

C언어 17. 응용자료형  (0) 2020.07.23
C언어 16. 변수의 영역  (0) 2020.07.22
C언어 14. 포인터 배열, 다중 포인터  (0) 2020.07.17
C언어 13. 문자열  (0) 2020.07.16
C언어 12. 배열과 포인터  (0) 2020.07.15