티스토리 뷰


본 강좌는 아래 동영상 강좌와 같이 진행됩니다. 되도록이면 동영상과 같이 보시는 것을 추천합니다.

  

유튜브 채널 가기

 

강좌 15편 동영상 보기

  


 

 이번시간에는 임의의 메모리 공간을 가져다 쓰는 '메모리 할당'에 대해 알아보도록 하겠습니다. 

 

 

 

 1. 메모리 할당

 

 메모리 할당이란 어떤 메모리 공간을 임의로 사용할 수 있도록 주는 것입니다.

 

int a = 0;

 

 위 변수의 선언은 a라는 변수가 임의의 메모리 공간을 int형 만큼 할당을 받아 거기에 0을 넣었다는 의미로 해석할 수 있습니다. 이런 식으로 알게 모르게 우리는 메모리를 할당 받아 사용해왔던 것입니다.

 

 메모리 할당에는 '정적 메모리 할당'과 '동적 메모리 할당'이 있으며, 차례대로 알아보도록 하겠습니다.

 

 ◆ 정적 메모리 할당

 위의 예의 int형 변수 a는 처음부터 크기가 정해진 공간을 할당 받아 사용하고, 변수가 소멸할때까지 이 공간의 크기를 바꾸지 못합니다. 이런 것을 '정적 메모리 할당' 이라고 합니다. 아래는 정적으로 메모리를 할당 받은 예입니다.

 

int a;

char str[10];

int score[20][20];

 

 일반 변수들과 배열이 여기에 포함됩니다.

 

 이러한 정적 메모리 할당은 다음과 같은 특성이 있습니다.

 

1. 선언과 동시에 크기가 정해지며, 중간에 바꿀 수 없다.

2. 소멸할때 운영체제가 자동으로 할당한 메모리를 회수한다.

 

 이런 특성을 생각하고 다음을 봅시다.

 

char name[3][20] = {"김철수", "오하영", "홍길동");

 

 char형 name이라는 2차원 배열에 3명의 이름을 저장하였습니다. 이 name이라는 배열은 정적이므로 이것으로는 3명까지만 담을 수 있고, 한 이름당 20개의 공간을 차지하기 때문에 실제 변수에 저장한 이름이 짧으면 짧을수록 메모리를 많이 낭비하게 됩니다.

 

 이런 단점들 때문에 '동적 메모리 할당' 이 필요하게 됩니다.

 

 ◆ 동적 메모리 할당

 동적 메모리 할당은 정적 메모리 할당과는 달리 프로그램의 중간에 메모리가 허용하는 범위내에서 원하는 만큼 메모리를 할당 받고 사용하고, 그 크기를 중간에 얼마든지 바꿀 수 있습니다. 동적 메모리 할당의 과정을 글로 적어보면 다음과 같습니다.

 

1. 포인터 변수를 선언한다.

2. 임의의 공간을 원하는 만큼 할당 받고 그 주소를 포인터에 저장한다.

3. 포인터를 가지고 할당받은 메모리 공간을 사용한다.

4. 다 쓰면 할당받은 메모리를 해제하여 운영체제에게 돌려준다. 

 

 위의 2~4번을 반복하면 한개의 포인터를 가지고 할당받는 메모리의 크기를 자유롭게 바꿀 수 있습니다. 주의해야 할 것은 4번의 메모리 해제를 반드시 해주어야 한다는 점입니다. 정적 메모리 할당에서는 이것을 해줄 필요가 없었지만 동적 메모리 할당을 사용할 때에는 일일히 해주어야 합니다. 이것을 해주지 않게 되면 프로그램이 사용할 메모리 공간을 그만큼 잃어버리게 되기 때문에 에러의 원인이 되거나 기타 여러가지 문제점이 발생할 소지가 있습니다.

 

 

 

 2. 동적 메모리 할당 방법

 

동적 메모리 할당을 사용하기 위해서는 우선 관련된 함수가 들어있는 해더 파일을 포함시켜야 합니다.

 

#include <stdlib.h>

 

 앞서 설명했던 동적 메모리 할당의 과정 1번에 해당하는 포인터 하나를 선언해봅시다.

 

char *str = NULL;

 

 char형 포인터 str을 선언하였고, NULL로 초기화를 해주었습니다.

 

 다음으로 2번에 해당하는 코드를 작성해보겠습니다.

 

str = (char*) malloc(20);

 

 여기서 malloc()이라는 것이 나왔습니다. 이것을 이용하면 괄호 안의 바이트만큼 메모리 공간을 만들어 그 포인터를 얻을 수 있습니다. 형식을 살펴보면

 

포인터를 받을 변수 = (자료형) malloc(할당받을 바이트 수)

 

 와 같이 사용합니다. 여기서 '자료형'은 '포인터를 받을 변수'의 자료형과 일치해야 합니다. 예를 들어 'int *'로 선언된 포인터 변수에 동적 메모리 할당된 포인터를 담기 위해서는 '자료형' 이라고 쓰인 부분에 'int*' 라고 적어주면 됩니다.

 

 이 부분은 'void*'에 대한 이해가 필요합니다. 그부분은 '함수'를 알아볼때 같이 알아보기로 하고, 우선은 자료형을 똑같이 맞춰준다고만 생각하시면 되겠습니다.

 

 그 다음 3번 항목처럼 동적 메모리 할당을 한 'str'을 이용해 일반 문자열 배열이나 포인터를 다루는 것과 똑같이 사용합니다.

 

strcpy(str, "안녕하세요.");

printf("%s\n", str);

 

 마지막으로 4번에 나와있는 것처럼 할당된 메모리를 해제 시켜줍니다.

 

free(str);

 

 free()의 괄호 안에 동적 메모리 할당된 포인터를 넣어주면 할당된 메모리를 다시 운영체제에 돌려주게 됩니다. 흔히 하는 실수가 이부분을 빼먹는건데, 그렇게 되면 동적 할당된 메모리가 많아질 수록 메모리를 많이 차지하게 되면서 운영체제나 프로그램이 느려지거나 메모리 오류를 발생할 수 있습니다. 이 부분은 반드시 해주어야 한다는걸 기억하시기 바랍니다.

 

 한번 다른 예를 들면서 정리해보겠습니다.

 

int *p = (int*) malloc(sizeof(int) * 3);

 

p[0] = 1;

p[1] = 2;

p[2] = 3;

 

printf("%d\t%d\t%d\n", p[0], p[1], p[2]);

 

free(p);

 

 int형 동적 메모리 할당을 받아 각각 1, 2, 3을 저장하고 출력한 후, 메모리 해제를 해주었습니다. int형을 3개만큼 할당하였는데, malloc()의 ()안에는 바이트수가 들어가기 때문에 int형의 크기를 sizeof() 연산자로 계산한 후 3을 곱해 주었습니다. 보통은 직접 숫자를 써주지 않고 이런식으로 sizeof()를 하여 곱해주는 식으로 많이 쓰니 참고하시기 바랍니다.

 

 

 

 3. 다중 포인터의 동적 메모리 할당

 

 동적 메모리 할당은 2차원 배열, 2차원 포인터 등과 같이 다중포인터를 사용하는 것이 가능합니다.

 

char **str = (char**) malloc(sizeof(char*) * 3);

str[0] = (char*) malloc(sizeof(char) * 5);

str[1] = (char*) malloc(sizeof(char) * 4);

str[2] = (char*) malloc(sizeof(char) * 3);

 

strcpy(str[0], "abcd");

strcpy(str[1], "efg");

strcpy(str[2], "hi");

 

free(str[0]);

free(str[1]);

free(str[2]);

free(str);

 

 첫번째 줄부터 살펴봅시다.

 

char **str = (char**) malloc(sizeof(char*) * 3);

 

 이중 포인터 str을 선언하였고, 거기에 동적 메모리 할당으로 char형 포인터를 3개만큼 할당해주었습니다. 포인터를 3개만큼 가지고 있다는 것은 지난 포인터 두번째 시간에서 잠깐 나왔던 '포인터 배열'과 같습니다. 그 다음 줄에는

 

str[0] = (char*) malloc(sizeof(char) * 20);

str[1] = (char*) malloc(sizeof(char) * 10);

str[2] = (char*) malloc(sizeof(char) * 5);

 

 각각의 포인터 배열의 원소들에 동적 메모리 할당을 해주었습니다. 각 원소들의 크기는 일반 2차원 배열과 달리 매우 자유롭게 지정할 수 있습니다. 여기까지를 일반 2차원 배열과 비교해서 그림을 그려보면

 


 아래쪽 배열부터 살펴봅시다. 배열은 2차원이든 3차원이든 연속된 메모리 공간에 할당이 되므로 그림과 깉이 일렬로 나란히 배치가 됩니다. 반면에 위쪽 그림인 포인터 배열의 경우는 각각이 서로 연속되지 않은 별도의 공간에 메모리가 할당이 되어 있고, 이 메모리 주소를 배열로 가지게 됩니다.

 

 그렇기 때문에 행과 열의 갯수가 고정적인 '정적 메모리 할당'인 2차원 배열은 위의 그림처럼 남는 공간이 ㅁ낳이 발생하게 되는 경우가 많습니다. 반면에 '동적 메모리 할당'을 사용한 이중 포인터의 경우 남는 공간 없이 효율적으로 메모리를 관리할 수 잇게 됩니다.

 

 그다음을 살펴봅시다.

 

strcpy(str[0], "abcd");

strcpy(str[1], "efg");

strcpy(str[2], "hi");

 

 이렇게 메모리 할당을 받은 다음에는 정해진 크기의 범위 내에서라면 얼마든지 배열처럼 사용할 수 있게 됩니다.

 

 모두 사용하고 난 다음 마지막에는 메모리 해제를 해줍니다.

 

free(str[0]);

free(str[1]);

free(str[2]);

free(str);

 

 각각 메모리를 할당 받은 만큼 해제를 해주어야 합니다. 만약 'free(str)'만 해주게 된다면 각각의 원소로 가지고 있던 동적 할당 받은 메모리는 해제가 되지 못하고 남게 될 것입니다. 해제 순서는 항상 가장 안쪽 원소들부터 해줍시다.

 

 

 

 4. 유용한 메모리 관련 함수들

 

 메모리 할당과는 직접적으로 관계는 없지만 메모리를 다루는데 유용한 함수 몇가지를 살펴봅시다.

 

 ◆ memset()

 

memset(포인터, 값, 바이트수);

 

 memset()은 '포인터'가 가리키는 주소부터 '바이트수'만큼을 '값' 으로 채웁니다. 보통은 초기화를 할때 다음과 같이 많이 씁니다.

 

char *str = (char*) malloc(sizeof(char) * 10);

memset(str, 0, sizeof(str));

 

 동적 메모리 할당 받은 str전체를 0으로 초기화 해줬습니다. 이렇게 되도록이면 메모리를 할당 받고 난 후 바로 memset()을 이용해 0으로 초기화 해주는 습관을 들이도록 합시다.

 

 ◆ memcpy()

 

memcpy(받을 포인터, 복사할 값이 들어있는 주소, 바이트수);

 

 memcpy()는 '받을 포인터'에 '복사할 값이 들어있는 주소'부터 시작해서 '바이트수' 만큼 복사합니다. 몇가지 예를 들어보겠습니다.

 

char str[20] = "안녕하세요.";

char str2[20];

 

memcpy(str2, str, sizeof(str));

 

 이런식으로 복사가 가능합니다.. 위와 같이 문자형 배열을 복사하는데는 strcpy()가 더 편할 것입니다.

 

char str[3][20] = {"안녕하세요.", "반가워요.", "안녕히 가세요."};

char str2[3][20];

 

memcpy(str2, str, sizeof(str));

 

 2차원 배열을 복사하는 경우에는 strcpy() 보다는 memcpy()가 더 편리합니다. 위의 예를 strcpy()로 사용할 경우엔 총 3번을 사용해야 하는데, memcpy()를 사용하여 간단히 한줄로 복사를 할 수 있었습니다.

 

 

 

 5. 성적관리 프로그램에 적용

 

 이번시간에 알아본 동적 메모리 할당을 적용해 봅시다. 학생들의 평가 부분은 입력 길이가 상당히 유동적일 수 있으므로 이부분을 고쳐보도록 하겠습니다. 우선 선언 부분을 다음과 같이 고쳐줍니다.

 

...

int scoreKOR[20] = {0};              // 국어점수

int scoreMAT[20] = {0};              // 수학점수

int scoreENG[20] = {0};              // 영어점수

int scoreSCI[20] = {0};               // 과학점수

char *comment[20] = { NULL };        // 평가

...

 

 원래 2차원 배열이었던 comment를 포인터 배열로 선언하였고, 각각의 원소를 NULL로 초기화 하였습니다.

 

 입력 받는 부분도 다음과 같이 고쳐줍시다.

 

...

 

switch (menuChoice)

{

case '1':

{

// 사용자 입력

 

...

 

getchar();

printf("평가를 입력하세요 : ");

gets_s(temp, sizeof(temp));

len = strlen(temp) + 1;

comment[index] = (char*) malloc(len);

strcpy_s(comment[index], len, temp);

index++;

}

 

break;

 

...

 

 이름을 입력받을 때 처럼 우선 temp에 입력을 받았습니다. 그 후에 malloc()을 이용해 동적 메모리 할당을 해주었습니다. 그 크기는 strlen()으로 길이를 얻고, 개행문자 들어갈 공간 한개를 추가했습니다.

 

 마지막으로 메모리 해제를 위해 다음을 추가합니다.

 

...

 

} while (menuChoice != 'Q' && menuChoice != 'q');

 

while(index)

{

index--;

free(comment[index]);

}

}

 

 while문을 이용해 각각의 원소를 모두 해제해 주었습니다. index 변수를 하나씩 감소시키면서 메모리 해제를 해주었고, 만약 index가 0이 되면 while문을 빠져나가게 됩니다.

 

 이것으로 성적관리 프로그램에 동적 메모리 할당을 적용해 보았습니다. 다음 시간에는 유용한 문자열 함수들에 대해 알아보도록 하겠습니다.


댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31