🔔 김성엽 님의 Do it! C언어 입문 동영상 강의를 보면서 정리한 내용입니다. 모든 그림과 코드 예제는 해당 강의에서 발췌한 내용입니다.
스택 메모리는 작은 크기(1Mb)와 모든 것이 컴파일타임에 정해진다는 한계점이 존재한다. 이를 보완하기 위해 동적 메모리 할당을 사용할 수 있다.
메모리의 스택세그먼트에는 지역변수에 할당되는 스택 영역 외에 힙 영역이 존재한다. 힙(Heap)은 동적 메모리 할당을 위한 영역인데 컴파일 타임이 아닌 프로그램 실행 중 언제든지 원하는 시점에 메모리 할당이 가능하고, 메모리 사용이 끝나면 언제든지 할당한 메모리를 해제할 수 있다. 또한 원하는 크기 만큼, GB 단위까지도 할당이 가능하다.
malloc 함수로 동적 메모리 할당하기
malloc(memory allocation) 함수를 사용해서 동적으로 메모리를 할당할 수 있고, 이를 사용하기 위해 malloc.h 파일을 포함해야 한다.
#include <malloc.h>
void *malloc(size_t size); // 함수 원형 *size_t는 unsigned int와 같은 자료형
void *p = malloc(100); // 함수 사용 형식의 예. 100바이트 할당 후 주소를 *p에 저장
malloc 함수는 메모리를 할당하고 그 메모리의 주소를 리턴한다. 메모리를 할당하는 시점에는 어떤 자료형을 가리키는 포인터가 될지 알 수 없기 때문에 void* 형으로 주소를 반환한다. 따라서 위와 같이 void *p 로 쓸 수 있지만, 실제로는 할당된 메모리 주소를 받는 시점에 미리 형 변환(casting)하여 사용한다.
short *p = (short *)malloc(100);
int *p = (int *)malloc(100);
free 함수로 할당된 메모리 해제하기
동적메모리할당을 통해 힙에 할당된 메모리는 프로그램이 끝날 때까지 자동해제 되지 않기 때문에 동적메모리 할당 후에는 free 함수를 사용하여 명시적으로 메모리를 해제해줘야 한다. free 함수 역시 사용하기 위해서 malloc.h 파일을 포함해야 한다.
#include <malloc.h>
free(p); // p는 메모리 주소를 가진 포인터. 즉 p가 가진 주소에 할당된 메모리를 해제 한다.
동적 메모리 할당을 사용하여 이름 입력받고 출력하기
동적메모리 할당이 항상 성공하는 것은 아니다. 할당에 실패하는 경우 malloc 함수는 메모리 주소가 아닌 NULL을 반환한다. 따라서 malloc 함수를 사용할 때는 항상 값이 NULL인지 체크하여 메모리 할당의 성패 여부를 확인한 후 진행해야 한다.
🚨 malloc 함수 사용시 주의할 점
- 할당만 하고 free를 하지 않으면 프로그램을 종료하기 전까지 힙 영역에 할당되는 메모리가 누적된다. 메모리 손실을 막기 위해 free를 하지 않는 경우가 생기지 않도록 주의! malloc과 free는 항상 습관적으로 한 쌍으로 작성할 것!
- 할당되지 않은 메모리를 해제하면 실행 시 오류 발생! (다시 한 번) malloc과 free는 항상 습관적으로 한 쌍으로 작성할 것!
- 동적 할당된 주소를 두 번 해제하면 (이미 해제한 주소를 또 해제하는 격이므로) 프로그램 실행 시 오류 발생
동적 메모리 할당의 장단점
장점
- 스택에 비해 큰 크기의 메모리 할당 가능
- 메모리를 할당하고 해제하는 시점을 지정 가능
- 할당되는 메모리의 크기를 프로그램 실행 중에 변경 가능
단점
- 코드가 복잡해짐
- 메모리를 기억하는 포인터 변수가 함께 사용되어야 하기 때문에 스택에 추가 4바이트 사용→ 작은 메모리를 사용할 때에는 비효율적
- 힙에 메모리를 할당/해제하는 과정들이 실제로 시간이 좀 소요되는 작업이기 때문에 너무 많이 쓰면 시간이 느려질 수 있음
배열과 비슷한 형식으로 동적메모리 사용하기
int *p = (int *)malloc(12);
위와 같이 메모리를 동적 할당한다는 것은 12바이트의 메모리를 4바이트씩 나눠서 3개의 int 값을 저장하겠다는 것이다. 이때 12바이트가 힙 영역에 할당되고, 이를 가리키는 포인터 p를 저장하기 위해 4바이트가 스택 영역에 할당된다. 이때 힙 영역에 저장되는 세 개의 int값을 *p, *(p+1), *(p+2) 로 접근할 수 있다.
💡위와 같이 동적메모리할당을 하면 힙 영역에 12바이트, 스택 영역에 4바이트가 할당되어 총 16바이트의 메모리를 사용한다. 반면 int data[3]; 과 같이 배열을 사용한다면 스택메모리에 12바이트만이 사용된다. 메모리 자체는 적게 쓰나 스택메모리의 한계가 존재한다 (스택의 최대 크기, 변경 불가 문제 등) 위에서 언급한 바와 같이 작은 메모리를 사용하는 데이터는 그냥 배열을 사용해서 지역변수로 스택에 할당하는 것이 좋다.
동적 메모리를 할당하는 또 다른 방법 - sizeof 사용
int *p = (int *)mallec(sizeof(int)*3);
똑같이 12바이트를 할당하더라도 직접 12를 명시하는 것보다는 위의 코드와 같이 sizeof(int)*3 을 사용해서 12가 할당되게 하는 것이 코드의 의도를 이해하하는 데에 더 도움이 된다.
💡배열을 사용하는 것보다 배열과 유사한 동적메모리 할당을 쓰는 게 더 좋은 또 다른 이유 : 유연함
int data_size =3;
int data[data_size]; // 오류!!!!
위와 같은 코드에서는 오류가 발생한다. 배열의 요소 갯수는 '상수'로만 명시가 가능하기 때문이다. 배열에 할당될 메모리는 컴파일 시점에 결정되어야 하는데 이는 프로그램 실행 중에 바뀔 수 없으므로 배열 요소의 개수를 변수로 받을 수 없는 것이다. 따라서 배열의 크기를 상수로 입력할때는 예상 가능한 최대치를 입력해야 하는데 실제로 사용되는 메모리가 그보다 작은 경우에는 메모리가 낭비되고, 더 큰 경우에는 소스를 수정하고 재컴파일해야한다. (낭비 OR 재컴파일 이라는 불편한 상황...)
→ 따라서 배열의 개수를 상수로 명시하기 어려운 경우에는 보다 유연한 동적메모리 할당을 하는 것이 좋다.
위의 코드에서는 최대 입력 횟수가 5보다 작으면 메모리 낭비, 5보다 크면 재컴파일을 해야 한다. 반면 malloc 함수에서는 메모리 할당 크기를 변수로 지정할 수 있어 필요한 만큼만 할당하고 언제든 수정할 수 있기 때문에 동적 메모리 할당이 보다 간편하다.
int data_size =12;
int *p = (int *)malloc(data_size); // 12바이트의 메모리를 동적으로 할당
Reference
https://www.youtube.com/watch?v=qw78mV28ROo&list=PLiZvlxkcLhakQwbPjkyfuHFy1IVG-VXrP&index=24
'C' 카테고리의 다른 글
[C] 메모리 할당 - 정적 메모리 할당 (김성엽의 기초 C언어 강좌 16장 1) (0) | 2022.10.28 |
---|---|
[C] 연결리스트 | 연결리스트란 / 연결리스트 생성 / 노드 삽입 / 검색 / 노드 삭제 (2) | 2022.10.24 |
[C] 문자열 리터럴 (문자열 상수) | 문자열 리터럴의 저장, 포인터, 문자열 변수와 문자열 상수 (1) | 2022.10.23 |
[C] 배열과 포인터 | 포인터와 배열의 관계, 배열 이름을 포인터처럼 사용하기, 포인터를 배열의 이름으로 사용하기 (0) | 2022.10.22 |
[C] 포인터 | 포인터란, 포인터 변수의 선언/초기화/호출, &연산자와 *연산자, 포인터의 연산, 포인터에 자료형이 필요한 이유 (0) | 2022.10.22 |
댓글