제 23강) 메모리 구조와 메모리 할당 |
오늘은 메모리 구조와 메모리 할당에 대해서 알아보는 시간입니다.
1) 메모리 구조 |
우리는 지금까지 변수를 써오면서 "메모리 공간에 할당"된다고 배웠습니다.
그럼 이 메모리는 어떠한 구조로 이루어져 있는가에 대해 고찰해봅시다.
메모리의 구조는 위의 그림과 같은 4가지 영역으로 나뉘어있습니다.
코드 영역(Code Area) |
코드가 있는 영역으로 실행하는 프로그램의 코드에 관련된 명령들이 있는 영역입니다. |
데이터 영역(Data Area) |
프로그램이 생성되었을 때 생성되고, 종료되었을 때 사라지는 데이터가 저장됩니다. (전역 변수, 혹은 정적 변수, 배열, 구조체 등이 저장됩니다.) |
힙 영역(Heap Area) |
사용자에게 할당되는 영역으로 직접 관리하는 영역입니다. 동적으로 메모리를 할당하거나 참조 변수가 이 영역에 있습니다. |
스택 영역(Stack Area) |
데이터가 가장 나중에 들어온 순서부터 맨위에 있는 영역으로 지역변수나 매개변수, 반환 값들이 저장됩니다. (Last In, First Out, 나중에 들어온 데이터가 먼저 나갑니다.) |
2) 메모리 동적 할당 |
메모리 동적 할당이라 함은 메모리를 Dynamic 하게 할당함을 뜻합니다.
간단히 말하자면 배열을 원하는 만큼 할당하는 것이지요.
즉, 다음과 같은 프로그램이 가능하다는 것입니다.
수를 입력 받아서 그 만큼 메모리를 할당 할 수 있다는 것이지요.
그래서 배열 동적할당이라고도 합니다.
2 - 1) malloc |
메모리를 동적으로 할당하는데 쓰이는 함수인 malloc 입니다.
#include <stdlib.h> // malloc을 사용하기 위함
// 기본 형태
void* malloc(size_t size); // size 크기 만큼 메모리를 할당함
// 예)
int* arr = (int*)malloc(sizeof(int) * 6); // 길이가 6인(int형) 동적 메모리공간을 할당함
기본 형태는 "void*"형 입니다만 malloc앞에 캐스팅연산을 하여 사용하면 됩니다.
(이때 반환되는 값은 성공적으로 배열을 만들었으면 해당 배열의 주소값이, 실패했다면 NULL값이 반환됩니다.)
할당할 때 크기는 할당하는 배열의 형에 맞게 sizeof를 한 다음에 원하는 길이만큼 곱해주면 됩니다.
#include <stdlib.h> // malloc을 사용하기 위함
// 예)
int* arr = (int*)malloc(sizeof(int) * 6); // 길이가 6인(int형) 동적 메모리공간을 할당함
char* arr = (char*)malloc(sizeof(char) * 12); // 길이가 12인(char형) 동적 메모리공간을 할당함
double* arr = (double*)malloc(sizeof(double) * 4); // 길이가 4인(double형) 동적 메모리공간을 할당함
이렇게 사용하면 됩니다.
그리고 malloc으로 할당한 배열은 free 함수를 이용해서 해제를 해야합니다.
2 - 2) free |
위에서 동적으로 할당한 배열을 해제(제거)하여봅시다.
#include <stdlib.h> // malloc을 사용하기 위함
// 기본 형태
void free(void* ptr);
// 예)
int* arr = (int*)malloc(sizeof(int) * 6); // 길이가 6인(int형) 동적 메모리공간을 할당함
free(arr); // 메모리 공간 해제
위와 같이 free를 이용하여 해제하면 됩니다.
이 해제 과정은 선택이 아닌 필수입니다.
그렇지 않으면 동적으로 할당한 메모리 공간이 해제되지 않아 메모리 누수가 발생합니다.
(즉, 자동으로 메모리가 비워지지 않습니다.)
이제 자신이 입력한 수 만큼 배열을 생성하는 예제를 만들어봅시다.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* arr;
int size;
printf("수를 입력하세요: ");
scanf("%d", &size);
arr = (int*)malloc(sizeof(int) * size);
if (arr == NULL)
printf("배열이 생성되지 않음\n");
else
{
for (int i = 0; i < size; i++)
arr[i] = i;
for (int i = 0; i < size; i++)
printf("arr[%d] = %d\n", i, arr[i]);
}
free(arr);
printf("메모리 해제 완료\n");
return 0;
}
항상 사용한 다음에 꼭 메모리를 해제해야합니다.(24번줄)
2 - 3) malloc(2차원배열) |
malloc을 이용하여 2차원배열도 동적으로 만들 수 있습니다.
하지만 1차원배열과는 달리 복잡합니다.
#include <stdlib.h> // malloc을 사용하기 위함
// 5 * 3 배열을 동적으로 할당하기
int **arr; // 생성할 2차원배열
arr = (int**)malloc(sizeof(int*) * 5); // 5행을 생성한 뒤
for (int i = 0; i < 5; i++)
{
arr[i] = (int*)malloc(sizeof(int) * 3); // 각각의 행에 3열씩 추가합니다.
}
// 생성한 메모리 해제
for (int i = 0; i < 5; i++)
{
free(arr[i]); // 각 행별로 선언된 열을 해제
}
free(arr); // 행을
이중포인터로 먼저 이차원배열을 생성할 것을 마련하고(5번줄)
행을 먼저 생성합니다.(6번줄)
그리고 각 행에 각각 열을 생성해줍니다.(7~10번줄)
사용한 후에 메모리를 해제해줍니다.(13~17번줄)
2 - 4) realloc |
malloc을 이용하여 만든 배열을 크기를 수정하여 다시 할당해주는 함수입니다.
#include <stdlib.h> // realloc을 사용하기 위함
// 기본 형태
void* realloc(void* ptr, size_t size); // ptr을 size크기로 재할당 함
// 예)
int* arr = (int*)malloc(sizeof(int) * 6); // 길이가 6인(int형) 동적 메모리공간을 할당함
arr = (int*)realloc(arr, sizeof(int) * 8); // arr을 길이가 8인(int형) 배열로 다시 재할당함
이때 반환값은 malloc과 같지만 한 가지 유의사항이 있습니다.
7번줄에서 8번줄로 넘어갈때 realloc이 실패할 경우에는 기존의 주소값을 잃어버리게 됩니다.
그래서 기존에 할당된 메모리를 free할 수 없어서 메모리 누수가 발생하게 됩니다.
그래서 realloc을 쓸때는 다음의 방법을 고려해야합니다.
// 기존의 배열을 남긴다.
#include <stdlib.h> // malloc을 사용하기 위함
// 예)
int* arr = (int*)malloc(sizeof(int) * 6);
int* arr2 = (int*)realloc(arr, sizeof(int) * 8); // 기존의 arr을 놔두고 새롭게 arr2를 만든다.
if (arr2 != NULL)
{
// 배열이 성공적으로 재할당되었다면 arr을 새로 재할당된 arr2를 할당한다.
arr = arr2;
}
배열을 재할당받아야 한다면 새로 배열을 만들고 그 후에 성공했을때 예전배열과 바꿔치기를 하는 것이지요.
(포인터이기에 가능한 연산입니다.)
만약에 실패했다면 기존의 배열을 그대로 가지고 있는 것이 됩니다.
이제 재할당 예제를 봅시다.
#include <stdio.h>
#include <stdlib.h>
#define BASIC_SIZE 5
void printArray(int _arr[], int _size)
{
for (int i = 0; i < _size; i++)
{
printf("%d", _arr[i]);
if (i != _size - 1)
printf(", ");
else
printf("\n");
}
}
int main()
{
int* arr = (int*)malloc(sizeof(int) * BASIC_SIZE);
int adder = 1;
int counter = 0;
int input = 0;
printf("(-1 선택시 배열 출력)\n");
do
{
printf("수를 입력하세요 : ");
scanf("%d", &input);
if (input == -1)
printArray(arr, counter);
else if (counter >= BASIC_SIZE * adder - 1)
{
printf("배열 재할당\n");
adder++;
int* arr2 = (int*)realloc(arr, sizeof(int) * BASIC_SIZE * adder);
if (arr2 != NULL)
{
arr = arr2;
arr[counter++] = input;
}
else
{
printf("배열 재할당 실패\n");
adder--;
break;
}
}
else
arr[counter++] = input;
} while (input != 0);
free(arr);
printf("메모리 해제 완료\n");
return 0;
}
정말 간단한 예제입니다.
처음에 배열은 BASIC_SIZE 크기인 5만큼 할당되어있습니다.
여기에 계속 수를 넣게 되면 counter가 증가하게 됩니다.
이 counter가 BASIC_SIZE * adder - 1보다 크거나 같게 된다면 즉, 배열 크기의 끝에 도달하게 되면 adder를 1증가 시킨 후에 배열을 BAIC_SIZE * adder의 크기로 재할당하게 됩니다.
이전의 배열이 5 * 1이었다면 이후의 배열의 크기는 adder가 1증가하기 때문에 5 * 2인 10의 크기를 가진 배열로 재탄생하게 됩니다.
2 - 4) calloc |
malloc과 정말 비슷하지만 아주 살짝 다릅니다.
#include <stdlib.h> // calloc을 사용하기 위함
// 기본 형태
void* calloc(size_t count, size_t type); // type형의 크기로 count 길이만큼 할당
// 예)
int* arr = (int*)calloc(6, sizeof(int)); // 길이가 6인(int형) 동적 메모리공간을 할당함
위의 선언은 int* arr = (int*)malloc(sizeof(int) * 6); 과 같습니다.
char* arr = (char*)calloc(6, sizeof(char));
위의 선언은 char* arr = (char*)malloc(sizeof(char) * 6); 과 같습니다.
malloc과 같지만 malloc은 길이를 곱해주는 방식으로 정했었다면, calloc은 인수로 직접 받게됩니다.
그 이후에 한 가지 더 다른점이 있는데
#include <stdio.h>
#include <stdlib.h>
#define BASIC_SIZE 6
void printArray(char str[], int _arr[], int _size)
{
printf("%s: ", str);
for (int i = 0; i < _size; i++)
{
printf("%d", _arr[i]);
if (i != _size - 1)
printf(", ");
else
printf("\n");
}
printf("\n");
}
int main()
{
// BASIC_SIZE = 6
int* arr_malloc = (int*)malloc(sizeof(int) * BASIC_SIZE);
int* arr_calloc = (int*)calloc(BASIC_SIZE, sizeof(int));
printArray("arr_malloc", arr_malloc, BASIC_SIZE);
printArray("arr_calloc", arr_calloc, BASIC_SIZE);
free(arr_malloc);
free(arr_calloc);
return 0;
}
위의 예제에서는 둘다 배열을 생성하고 나서 바로 출력을 하는 예제입니다.
이렇게 malloc으로 할당한 배열은 쓰레기 값이 들어가지만
calloc으로 할당한 배열은 0으로 자동 초기화가 됨을 알 수 있습니다.
그런데 GCC에서는 둘다 0으로 초기화하여 처리하는 것 같습니다.
다음 시간에는 |
오늘은 메모리 동적 할당에 대해서 알아보았습니다.
다음 시간에는 구조체에 대해서 알아볼것입니다.
'Study > C언어' 카테고리의 다른 글
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 25 [공용체와 열거형(union, enum)] (0) | 2018.10.28 |
---|---|
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 24 [구조체와 형식 정의 지정자(struct, typedef)] (0) | 2018.10.28 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 21 [다차원배열1 - 다차원배열의 기본(2차원, 3차원)] (0) | 2018.10.17 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 22 [다차원배열2 - 다차원배열과 포인터, 이중 포인터, 다중 포인터] (0) | 2018.10.17 |
처음하시는 분들을 위한 C언어 기초강의 시즌2 - 17 [포인터] (0) | 2018.10.17 |