언뜻 생각해보면 전역변수와 지역변수만 있으면 충분하다는 생각
그러나 프로그램을 구현하다 보면 이 둘이 아닌 다른 유형의 변수를 필요로 하게 됨
전역변수와 지역변수로 해결이 되지 않는 상황
아래는 문제가 있다
#include <stdio.h>
char* readusername(void)
{
char name[30];
printf("What's your name? ");
gets(name);
return name; // 무엇을 반환하는가?
}
int main(void)
{
char* name1;
char* name2;
name1 = readusername();
printf("name1: %s \n", name1);
name2 = readusername();
printf("name1: %s \n", name2);
return 0;
}
문제점은 무엇인가?
- 문자열이 저장되어 있는 배열이(name[30]) 지역적으로 선언되었기 때문에 함수를 빠져나오면서 소멸된다는데 있다.
- 그래서 실제로 실행을 해보면 정상적이지 못한 결과로 이어지는 것을 확인
전역변수를 이용해서 해결?? → 이것도 문제가 있음
#include <stdio.h>
char name[30];
char* readusername(void)
{
printf("What's your name? ");
gets(name);
return name; // 무엇을 반환하는가?
}
int main(void)
{
char* name1;
char* name2;
name1 = readusername();
printf("name1: %s \n", name1);
name2 = readusername();
printf("name1: %s \n", name2);
printf("name1: %s \n", name1);
printf("name1: %s \n", name2);
return 0;
}
문제점
- 하나의 전역변수(전역으로 선언된 배열)을 이용하면, 이 전역변수를 덮어쓰게 된다.
- 함수호출을 통해서 얻게 된 이름정보가 유지되지 않는다.
즉 프로그램 사용자에게서 이름정보를 입력 받아서 이를 반환하는 함수를 정의하기에는 지역변수도 전역변수도 답이 될 수 없다.
그렇다면 어떠한 성격의 변수가 필요한 것?
"함수가 매번 호출될 때마다 새롭게 할당되고 또 함수를 빠져나가도 유지가 되는 유형의 변수"
다시 말해서, 지역변수와 같이 함수가 호출될 때마다 매번 할당이 이뤄지지만, 할당이 되면 전역변수와 마찬가지로 함수를 빠져나가도 소멸되지 않는 성격의 변수가 필요하다.
이렇게 생성과 소멸의 시기가 지역변수나 전역변수와 다른 유형의 변수는 "malloc"과 "free"라는 이름의 함수를 통해서 힙 영역에 할당하고 소멸할 수 있다.
힙 영역의 메모리 공간 할당과 해제: malloc과 free 함수
malloc 함수를 이용해서 메모리 공간을 할당하고, 또 할당된 메모리 공간은 free 함수의 호출을 통해서 해제한다.
#include <stdlib.h>
void* malloc(size_t size); // 힙 영역으로의 메모리 공간 할당
void free(void* ptr); // 힙 영역에 할당된 메모리 공간 해제
- malloc 함수는 성공 시 할당된 메모리의 주소 값, 실패 시 NULL 반환
힙 영역을 흔히 '프로그래머가 관리하는 메모리 공간'
malloc 함수호출로 할당된 메모리 공간은 프로그래머가 직접 free 함수의 호출을 통해서 해제하지 않으면 계속 남아있기 때문
즉 위의 두 함수는 다음과 같이 쌍을 이루어 호출
int main(void)
{
void* ptr1 = malloc(4); // 4바이트가 힙 영역에 할당
void* ptr2 = malloc(12); // 12바이트가 힙 영역에 할당
....
free(ptr1); // ptr1이 가리키는 4바이트 메모리 공간 해제
free(ptr2); // ptr2이 가리키는 12바이트 메모리 공간 해제
....
}
malloc 함수는 인자로 전달된 정수 값에 해당하는 byte 크기의 메모리 공간을 힙 영역에 할당하고, 이 메모리 공간의 주소 값을 반환한다.
따라서 위의 코드를 실행하게 되면
ptr1은 첫 번째 malloc 함수호출을 통해서 할당된 메모리 공간의 첫 번째 byte를 가리키게 되고
ptr2은 두 번째 malloc 함수호출을 통해서 할당된 메모리 공간의 첫 번째 byte를 가리키게 된다.
이어서
ptr1을 인자로 free 함수를 호출하는 시점에 ptr1이 가리키는 메모리 공간이 소멸되고,
ptr2를 인자로 free 함수를 호출하는 시점에 ptr2가 가리키는 메모리 공간이 소멸된다.
무엇보다도 malloc 함수와 free 함수의 호출위치 및 시점에는 제한이 없다. 원하는 시점에 할당하고 원하는 시점에 소멸이 가능
"힙에 할당된 메모리 공간은 포인터 변수를 이용해서 접근하는 방법밖에 없음?"
malloc 함수는 주소 값을 반환한다. 그리고 그 주소 값을 이용해서 힙에 접근을 해야 한다. 따라서 포인터를 이용해서 메모리 공간에 접근하는 수밖에 없다. 그래서 이제 malloc 함수의 반환형에 관심을 두어야 함.
malloc 함수의 반환형이 void형 포인터인 이유와 힙 영역으로의 접근
malloc 함수의 반환형 void형 포인터
void형 포인터는 주소 값을 담는 바구니에 지나지 않는다고 하였음
따라서 malloc 함수의 반환 값에 아무런 가공도 가하지 않으면, 이를 이용해서는 할당된 메모리 공간에 접근이 불가능 하다.
void* ptr = malloc(sizeof(int)); // int형 변수 크기의 메모리 공간 할당
*ptr=20; // ptr이 void형 포인터이므로 컴파일 에러
왜 반환형이 void인거임?
- malloc은 포인터 형을 결정할 수 없음
- malloc에게 전달하는 것은 숫자뿐
- 4를 전달하면 int형 변수로 사용할지, float형 변수로 사용할지, 길이가 4인 char형 배열로 사용할지 알 수 없음
- 따라서 void형 포인터로 반환
사실 아래의 코드는
void * ptr1 = malloc(sizeof(int)); // int형 변수 크기의 메모리 공간 할당
void * ptr2 = malloc(sizeof(double)); // double형 변수 크기의 메모리 공간 할당
void * ptr3 = malloc(sizeof(int) * 7); // 길이가 7인 int형 배열로 사용할 공간 마련
void * ptr4 = malloc(sizeof(double) * 9); // 길이가 9인 double형 배열로 사용할 공간 마련
아래와 같다
void * ptr1 = malloc(4);
void * ptr2 = malloc(8);
void * ptr3 = malloc(28);
void * ptr4 = malloc(72);
"원하는 크기만큼 메모리 공간을 할당하고 그 메모리의주소 값을 반환하겠음. 그러니 어떻게 사용할지는 포인터 형의 변환을 통해서 직접 결정"
int * ptr1 = (int *)malloc(sizeof(int));
double * ptr2 = (double *)malloc(sizeof(double));
int * ptr3 = (int *)malloc(sizeof(int) * 7);
double * ptr4 = (double *)malloc(sizeof(double) * 9);
예제
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* ptr1 = (int* )malloc(sizeof(int));
int* ptr2 = (int*)malloc(sizeof(int) * 7);
int i;
*ptr1 = 20;
for (i = 0; i < 7; i++)
ptr2[i] = i + 1;
printf("*ptr1 : %d \n", *ptr1);
printf("ptr2[7] : ");
for (i = 0; i < 7; i++)
printf("%d ", ptr2[i]);
printf("\n");
free(ptr1);
free(ptr2);
return 0;
}
malloc 함수는 메모리 공간의 할당에 실패할 경우 NULL을 반환한다.
따라서 메모리의 할당 성공여부를 확인하고자 한다면 다음과 같이 코드를 작성
int* ptr = (int *)malloc(sizeof(int));
if(ptr==NULL)
{
// 메모리 할당 실패에 따른 오류의 처리
}
malloc 함수의 호출을 통한 메모리 공간의 할당을 가리켜 '동적 할당(dynamic allocation)'이라 한다.
이유는 할당되는 메모리 크기를 컴파일러가 결정하지 않고, 프로그램 실행 중간에 호출되는 malloc 함수가 결정하기 때문
free 함수를 호출하지 않으면 프로그램 종료 후에도 메모리가 남게 되나?
그렇지는 않음
프로그램 실행 시 할당된 모든 메모리 공간은 프로그램이 종료되면 운영체제에 의해서 전부 해제가 된다.
그렇더라도 free 함수를 습관적으로 호출하는 게 좋음.
(아무래도 프로그램 규모가 커지면 메모리 낭비가 많아져서 그럴수도)
문자열을 반환하는 함수를 정의하는 문제의 해결
"함수가 호출될 때마다 문자열 저장을 위한 메모리 공간의 할당이 가능해야 하고 이 메모리 공간은함수를 빠져나가지도 소멸되지 않고 존재해야 한다"
malloc 함수와 free 함수의 호출을 통해서 해결해야 하는 문제
#include <stdio.h>
#include <stdlib.h>
char* readusername(void)
{
char* name = (char*)malloc(sizeof(char) * 30);
printf("what is your name? ");
gets(name);
return name;
}
int main(void)
{
char* name1;
char* name2;
name1 = readusername();
name2 = readusername();
printf("name1 : %s \n", name1);
printf("name2 : %s \n", name2);
free(name1);
free(name2);
return 0;
}
이렇듯 malloc 함수와 free 함수를 이용하면 메모리 공간의 할당과 소멸의 시점을 프로그래머가 직접 결정할 수 있다.
때문에 전역변수나 지역변수가 감당하지 못하는 일들을 감당할 수 있다.
malloc 함수의 사촌 뻘 되는 calloc 함수
calloc과 malloc 함수와의 유일한차이점은 메모리 공간의 할당을 위한 인자의 전달방식
#include <stdlib.h>
void* calloc(size_t elt_count, size_t elt_size);
malloc 함수의 전달인자는 하나였다.
"총 120 바이트를 힙 영역에 할당해 주세요"
반면 calloc 함수의 첫 번째 전달인자로는 할당할 블록의 갯수 정보가 전달되고, 두 번째 전달인자로는 블록 하나당 바이트 크기의 정보가 전달된다.
"4바이트 크기의 블록(elt_size) 30개를(elt_count) 힙 영역에 할당해 주세요"
120바이트를 할당해 달라는 것과, 4바이트 크기의 블록 30개를 할당해 달라는 것은 결과적으로 완전히 동일.
calloc 함수는 malloc 함수와 인자를 전달하는 방식에서 차이를 보인다.
한가지 차이점이 더 있다.
malloc 함수 : 할당된 메모리 공간을 별도의 값으로 초기화하지 않는다. 따라서 할당된 메모리 공간은 쓰레기 값
calloc 함수 : 할당된 공간의 모든 비트를 0으로 초기화 시킨다.
둘 함수 모두 메모리를 해제할 때 free 함수를 사용하면 된다.
힙에 할당된 메모리 공간 확장 시 호출하는 realloc 함수
한번 할당된 메모리 공간은 그 크기를 확장할 수 없다.
이는 모든 영역의 메모리 공간에 해당하는 말이다.
이미 할당되어버린 배열의 길이를 늘릴 수 있는가?
어느 영역에 선언을 하건 간에 이러한 일은 불가능
하지만 그 영역이 힙이라면, 그리고 realloc 함수를 사용한다면 이러한 일이 가능
#include <stdlib.h>
void * realloc(void * ptr, size_t size);
- 성공 시 새로 할당된 메모리의 주소 값, 실패 시 NULL 반환
첫 번째 전달인자 : 확장하고자 하는 힙 메모리의 시작 주소 값을 전달한다.
두 번쨰 전달인자 : 확장하고자 하는 메모리의 전체 크기를 전달
"ptr이 가리키는 메모리의 크기를 size 크기로 조절해줘(늘려줘)"
함수호출의 성공 시에는 새로 할당된 메모리의 주소 값이 반환되고, 실패 시 NULL이 반환
int main(void)
{
int* arr = (int *)malloc(sizeof(int)*3); // 길이가 3인 int형 배열 할당
....
arr = (int *)realloc(arr, sizeof(int)*5); // 길이가 5인 int형 배열로 확장
....
}
반환 값을 기준으로 두 가지로 구분
"malloc 함수가 반환한 주소 값과 realloc 함수가 반환한 주소 값이 같은 경우"
"malloc 함수가 반환한 주소 값과 realloc 함수가 반환한 주소 값이 같지 않은 경우"
전자는 기존에 할당된 메모리 공간의 뒤를 이어서, 확장할 영역이 넉넉한 경우
하지만, 넉넉하지 않은 경우에 힙의 다른 위치에, 새로 요구하는 크기의 메모리 공간을 별도로 할당해서 이전 배열에 저장된 값을 복사하기도 함.
그리고 이러한 경우에는 후자의 경우와 같이 malloc 함수와 realloc 함수의 반환 값이 같지 않다.
참고 : [윤성우 열혈 C 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.
'Language&Framework&Etc > C' 카테고리의 다른 글
매크로와 선행처리기(26-2) 대표적인 선행처리 명령문 (0) | 2020.12.17 |
---|---|
매크로와 선행처리기(26-1) 선행처리기와 매크로 (0) | 2020.12.17 |
메모리 관리와 메모리의 동적 할당(25-1) C언어의 메모리 구조 (0) | 2020.12.17 |
파일 입출력(24-5) 임의 접근을 위한 '파일 위치 지시자'의 이동 (0) | 2020.12.17 |
파일 입출력(24-4) 텍스트 데이터와 바이너리 데이터를 동시에 입출력 하기 (0) | 2020.12.17 |