일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 장고
- AWS
- js
- DRF
- django ORM
- c++
- 알고리즘 연습
- java
- 알고리즘
- django widget
- CSS
- 알고리즘 문제
- form
- API
- react
- 알고리즘 풀이
- Algorithm
- HTML
- web
- MAC
- javascript
- PYTHON
- 파이썬 알고리즘
- 백준
- es6
- 파이썬
- Baekjoon
- django rest framework
- Git
- Django
- Today
- Total
수학과의 좌충우돌 프로그래밍
C언어 - 메모리 동적할당 본문
안녕하세요 강민성 입니다.
오늘은 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); } |
한 줄씩 살펴보도록 하겠습니다.
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 int, unsigned 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(size, sizeof(int)); while(1) { printf("양수를 입력하세요.\n"); scanf("%d",&num); if (num<0) break; 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(size, sizeof(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 | 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; | 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 | i = 0; while (str[i] != 0) { free(str[i]); i++; } free(str); | cs |
마지막으로 메모리를 반환하는 부분입니다.
이 역시도 널 포인터가 나올 때까지 str[i]를 반환해주고,
str도 따로 동작할당 했으므로 따로 반환해주도록 합니다.
마무리
오늘은 c언어 동적할당에 대해서 알아보았습니다.
사실 동적할당을 안쓰더라도 당장의 간단한 프로그램을 구성하는데는 아무 문제가 없을 겁니다.
하지만 좀 더 나은 프로그래밍을 하기 위해서는 필수적으로 동적할당을 알아야 할 것 입니다.
이번 포스팅이 동적할당을 공부하는데 도움이 되었기를 바라며
글을 마치도록 하겠습니다.
'프로그래밍 언어 > C' 카테고리의 다른 글
C언어 - 사용자 정의 자료형 (0) | 2018.12.30 |
---|