수학과의 좌충우돌 프로그래밍

C언어 - 메모리 동적할당 본문

프로그래밍 언어/C

C언어 - 메모리 동적할당

ssung.k 2018. 12. 28. 20:35



안녕하세요 강민성 입니다.


오늘은 C언어 메모리 동적 할당에 대해 알아보도록 하겠습니다.


우리가 프로그램을 작성할 때 변수가 배열을 선언 해줌으로서


저장 공간을 확보하게 됩니다.


처음부터 얼마 만큼의 메모리를 사용할지 안다면 문제가 없겠죠.


하지만 프로그램의 실행 도중, 저장 공간을 할당해야 하는 경우도 생길 것 입니다.


그럴 때 오늘 배울 메모리 동적 할당을 사용하게 됩니다.




메모리 동적 할당에 대해 알아보자!





1. malloc , free 함수


먼저 malloc 함수와 free 함수부터 알아보도록 하겠습니다.


우선 두 함수를 사용하기 위해서는 stdlib.h 헤더 파일을 include 해야 합니다.





1
void *malloc (unsigned int size);
cs


다음은 malloc 함수의 원형입니다.


인자로 메모리 할당 크기를 받게 되고, 반환값으로 할당된 메모리의 주소를 반환합니다.


반환값의 유형은 void * 입니다.



1
void free(void *p);
cs


다음은 free 함수의 원형입니다.


인자로 void 포인터를 받게 됩니다.


free 함수는 항상 동적할당 함수들과 같이 붙어다니게 되는데,


그 이유는 다음과 같습니다.


지역 변수 같은 경우에는 함수가 반환 될 때 변수의 저장 공간을 자동으로 회수하게 됩니다.


하지만 동적으로 할당한 저장공간은 다음과 같이 free 함수를 통해 수동으로 회수를 해줘야 합니다.


그러면 좀 더 근본적으로 들어가서 왜 메모리를 회수 해줘야 하는 걸까요?


사실 free 함수를 사용하지 않더라도 지금 당장은 프로그램 실행에 큰 문제를 미치지 않습니다.


하지만 이것이 쌓이고 쌓이다 보면 메모리 누수가 발생하게 됩니다.



메모리 누수?


동적할당을 하게 되면 데이터가 힙 영역  저장이 됩니다.


힙 영역은 메모리의 사용과 반환이 불규칙적 이기 때문에 사용 가능한 공간이 조각조각 나있을 수 있습니다.


아래 그림을 보도록 하겠습니다.


힙영역에 아직 남은 공간이 많이 있지만 공간들이 조각나있기 떄문에 


원하는 크기의 데이터를 저장할 수 없는 상황이 발생합니다.


이를 메모리 누수라고 하고 이런 상황을 막기 위해서 free 함수를 통해 


메모리를 반환하게 되는 겁니다. 



그러면 이제 malloc과 free를 사용한 예제를 보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int *pi;
    pi = (int *)malloc(sizeof(int));
    if (pi == NULL)
    {
        printf("메모리가 부족합니다.\n");
        exit(1);
    }
    *pi = 10;
    printf("%d\n",*pi); // 10
free(pi);
}

cs



한 줄씩 살펴보도록 하겠습니다.


1
pi = (int *)malloc(sizeof(int));
cs


malloc함수의 원형을 살펴보면 메모리의 크기를 받아서 void * 를 반환하게 됩니다.


동적할당을 해줄 변수는 pi 이므로 pi의 형태에 맞게 int의 크기만큼 메모리를 할당해주고,


int * 로 형변환을 해줍니다. 


1
2
3
4
5
if (pi == NULL)
    {
        printf("메모리가 부족합니다.\n");
        exit(1);
    }
cs


아까 말했던 메모리 누수와 관련된 코드 입니다.


malloc을 포함한 메모리 할당 함수들은 원하는 크기의 공간 할당을 하지 못하면


null pointer를 반환하게 됩니다.


따라서 이 경우, 메모리가 부족하다는 메세지와 함께 exit를 통해 프로그램을 바로 종료하게 됩니다.



1
free(pi);
cs



마지막으로 저장공간을 수동으로 회수해줌으로서 메모리 누수를 막아줍니다.


사실 방금과 같은 상황에서는 free함수를 사용하지 않아도 상관이 없습니다.


그 이유는, 프로그램이 끝나면, 


즉 main 함수가 끝나면 모든 데이터가 반환되기 때문에 동적할당된 메모리 영역도 반환이 됩니다.


하지만 습관적으로 free함수 쓰시는걸 추천드립니다. 


 

2. calloc , realloc 함수



다음으로는 다른 동적할당 변수인 calloc과 realloc 에 대해서 알아보도록 하겠습니다.



1
void *calloc(unsigned intunsigned int);
cs



calloc 함수의 원형입니다.


malloc 함수와 전체적인 동작원리는 동일하지만 다른 점이 몇 가지 있습니다.


첫 번째로 두 개의 인자를 받고 두 인자의 곱만큼 메모리를 동적할당하게 됩니다.


두 번째로는 메모리 동적 할당시, 0으로 초기화된 메모리 공간을 얻게 됩니다.



1
void *realloc (void *unsigned int);
cs



realloc 함수의 원형입니다.


마찬가지로 비슷한 역할을 하지만


void * 와 크기를 인자로 받아서 void *의 크기를 바꿔주는 역할을 합니다.



두 함수 역시도 예제를 통해 살펴보도록 하겠습니다.



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
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
    int *pi;
    int size = 5;
    int cnt = 0;
    int num;
    int i;
    
    pi = (int *)calloc(sizesizeof(int));
    while(1)
    {
        printf("양수를 입력하세요.\n");
        scanf("%d",&num);
        if (num<0break;
        if (cnt < size)
        {
            pi[cnt] = num;
            cnt++;
        }
        else
        {
            size += 5;
            pi = (int *)realloc(pi,size*sizeof(int));
            pi[cnt] = num;
            cnt++;
        }
    }
    for (i=0;i<cnt;i++)
    {
        printf("%3d",pi[i]);
    }
    free(pi);
}
 
cs



음수를 입력받으면 프로그램을 종료하고, 양수를 계속해서 입력받는 프로그램입니다.


사용자가 양수를 얼마나 입력할 지 모르기 때문에 메모리를 동적할당해서 사용을 합니다.


한 줄씩 살펴보도록 하겠습니다.


1
pi = (int *)calloc(sizesizeof(int));
cs


먼저 calloc함수로 pi를 동적할당 하였습니다.


동적할당한 크기는 size * sizeof(int), size 는 5이므로 정수 5개가 들어갈 만큼 동적할당을 합니다.



1
2
3
4
5
if (cnt < size)
{
    pi[cnt] = num;
    cnt++;
}
cs


하나의 값을 받을 때마다 cnt를 하나씩 증가시키게 됩니다.


size는 5이므로 5개의 양수를 받을 때 까지는 다음과 같은 if문에서 값을 받게 됩니다.


또한 동적할당한 영역은 3번째 줄과 같이 배열처럼 사용이 가능합니다.


1
2
3
4
5
6
7
else
{
    size += 5;
    pi = (int *)realloc(pi,size*sizeof(int));
    pi[cnt] = num;
    cnt++;
}
cs



cnt가 size보다 같거나 커졌을 경우에는, 다음과 같은 동작을 취하게 됩니다.


먼저 size의 크기를 5증가시켜주고 realloc을 통해서 pi를 다시 동적할당 해주게 됩니다.


이번에는 10개의 정수를 받을 수 있도록 동적할당이 되는 것이죠,





3. 동적 할당 활용 방법


지금까지 동적할당 함수들과 그 예시에 대해 살펴보았습니다.


마지막으로 이를 활용한 예시를 통해 마무리 하도록 하겠습니다.


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void print_str(char **);
int main(void)
{
    char temp[80];
    char **str;
    int max;
    int i;
    
    printf("몇 개의 문자열을 입력하시겠습니까?\n");
    scanf("%d",&max);
    getc(stdin);
    str = (char **)malloc((max+1)*sizeof(char *));
    
    i = 0;
    while(1)
    {
        printf("문자열을 입력하세요.\n");
        gets(temp);
        printf("%s",temp);
        if (temp[0== '\0')
            break;
        str[i] = (char *)malloc(strlen(temp)+1);
        strcpy(str[i],temp);
        i++;
        if (i == max)
        {
            printf("다 채웠습니다.\n");
            break;
        }
    }
    str[i] = 0;
    
    print_str(str);
    
    i = 0;
    while (str[i] != 0)
    {
        free(str[i]);
        i++;
    }
    free(str);
    return 0;
}
 
void print_str(char **ps)
{
    while (*ps != 0)
    {
        printf("%s\n",*ps);
        ps ++;
    }
}
 
cs



사용자가 원하는 갯수 만큼 문자열을 받는 프로그램입니다.


몇 개의 문자열을 입력할지, 문자열의 길이가 어느정도 일지 모르기 때문에


두 부분 모두 동적할당을 사용하고 있습니다.


첫번째 동적할당 부분부터 본다면,


1
2
3
4
    printf("몇 개의 문자열을 입력하시겠습니까?\n");
    scanf("%d",&max);
    getc(stdin);
    str = (char **)malloc((max+1)*sizeof(char *));
cs


사용자로부터 몇 개의 문자열을 입력할지, max에 값을 받아 max보다 1만큼 더 동적할당하게 됩니다.


이렇게 한 이유는, 마지막 저장공간에 널 포인터를 채워 끝을 표시하기 위해서 입니다.


3번째 줄에 getc를 사용한 이유는, scanf로 받을 시 버퍼에 '\n'이 남아 있어


 뒤에서 나올 gets에 영향을 미치기 때문에 미리 버퍼를 비워두었습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
= 0;
    while(1)
    {
        printf("문자열을 입력하세요.\n");
        gets(temp);
        printf("%s",temp);
        if (temp[0== '\0')
            break;
        str[i] = (char *)malloc(strlen(temp)+1);
        strcpy(str[i],temp);
        i++;
        if (i == max)
        {
            printf("다 채웠습니다.\n");
            break;
        }
    }
    str[i] = 0;


cs



break가 되는 지점을 보면, temp[0] == '\0'일 경우, 즉 enter만을 입력했을 때와


i == max  인 경우, 즉 사용자의 입력값 max만큼 문자열을 입력했을 때 입니다.


여기서도 사용자가 입력한 temp의 길이 + 1 만큼 str[i]를 동적할당하게 되는데


+1을 해주는 이유는 문자열 맨 뒤에 널문자 때문입니다.


마지막으로 while 문이 끝나면 마지막에 널 포인터를 대입하여,  끝을 표시해줍니다.


1
2
3
4
5
6
7
8
void print_str(char **ps)
{
    while (*ps != 0)
    {
        printf("%s\n",*ps);
        ps ++;
    }
}
cs


사용자가 입력한 문자열을 출력해주는 서브함수입니다.


마지막을 널 포인터로 채워주었으니 널 포인터가 나올 때까지 반복해주며 출력을 합니다.



1
2
3
4
5
6
7
= 0;
    while (str[i] != 0)
    {
        free(str[i]);
        i++;
    }
    free(str);
cs


마지막으로 메모리를 반환하는 부분입니다.


이 역시도 널 포인터가 나올 때까지 str[i]를 반환해주고,


str도 따로 동작할당 했으므로 따로 반환해주도록 합니다.




마무리


오늘은 c언어 동적할당에 대해서 알아보았습니다.


사실 동적할당을 안쓰더라도 당장의 간단한 프로그램을 구성하는데는 아무 문제가 없을 겁니다.


하지만 좀 더 나은 프로그래밍을 하기 위해서는 필수적으로 동적할당을 알아야 할 것 입니다.


이번 포스팅이 동적할당을 공부하는데 도움이 되었기를 바라며


글을 마치도록 하겠습니다.

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

C언어 - 사용자 정의 자료형  (0) 2018.12.30
Comments