happy coding

[c] 메모리 관리 본문

self study/언어의 기본부터

[c] 메모리 관리

yeoonii 2022. 11. 22. 17:57
TCP school을 보고 정리한 내용입니다.

메모리 구조

프로그램이 실행되기 위해서는 먼저 프로그램이 메모리에 load되어야 한다. 또한 프로그램에서 사용되는 변수들을 저장할 메모리도 필요하다. 메모리 공간은 1. 코드(code)영역 2. 데이터(data) 영역 3. 스택(stack) 영역 4. 힙(heap) 영역 이 있다.

1. 코드 영역 은 실행할 프로그램의 코드가 저장되는 영역으로 텍스트 영역이라고도 부른다. CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리하게 된다.

2. 데이터 영역 은 프로그램의 전역 변수와 정적 변수가 저장되는 영역이다. 데이터 영역은 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 소멸한다.

3. 스택 영역 은 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역이다. 스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸한다. 이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 한다. 스택 영역은 푸시(push) 동작으로 데이터를 저장하고, 팝(pop) 동작으로 데이터를 인출한다. 이러한 스택은 후입선출(LIFO, Last-In First-Out) 방식에 따라 동작하므로, 가장 늦게 저장된 데이터가 가장 먼저 인출된다. 스택 영역은 메모리의 높은 주소에서 낮은 주소의 방향으로 할당된다.

4. 힙 영역 은 사용자가 직접 관리할 수 있고 관리해야만 하는 메모리 영역이다. 힙 영역은 사용자에 의해 메모리 공간이 동적으로 할당되고 해제되며, 낮은 주소에서 높은 주소로 할당된다. 

스택 프레임(stack frame)

함수가 호출되면 스택에는 함수의 매개변수, 호출이 끝난 뒤 돌아갈 반환 주소값 그리고 함수에서 선언된 지역 변수 등이 저장되는데, 이렇게 스택 영역에 차례대로 저장되는 함수의 호출정보를 스택 프레임이라고 한다. 이러한 스택 프레임 덕분에 함수의 호출이 모두 끝난 뒤 해당 함수가 호출되기 이전 상태로 되돌아갈 수 있다.

스택 오버플로우(stack overflow)

함수의 재귀 호출이 무한히 반복되면, 해당 프로그램은 스택 오버플로우에 의해 종료되는데, 이는 스택의 모든 공간을 다 차지하고 난 후 더 이상의 여유 공간이 없을 때 또 다시 스택 프레임을 저장하게 되면 해당 데이터는 스택 영역을 넘어 저장하게 되는데 이로써 오동작을 하게 되거나 보안상의 크나큰 취약점을 가지게 되기 때문입니다. 

메모리의 동적 할당(dynamic allocation)

데이터 영역과 스택 영역에 할당되는 메모리의 크기는 컴파일 타임에 미리 결정되지만 힙 영역의 크기는 프로그램이 실행되는 도중인 런 타입에 사용자가 직접 결정하게 된다. 이렇게 런 타임에 메모리를 할당받는 것을 메모리의 동적 할당이라고 한다.

malloc() 함수

malloc() 함수는 프로그램이 실행 중일 때 사용자가 직접 힙 영역에 메모리를 할당할 수 있게 해준다.

#include <stdlib.h>
void *malloc(size_t size);	//size_t 타입은 부호없는 정수

위 코드는 malloc() 함수의 원형이다. malloc() 함수는 인수로 할당받고자 하는 메모리의 크기를 바이트 단위로 전달받는데, 이 함수는 전달받은 메모리 크기에 맞고, 아직 할당되지 않은 적당한 블록을 찾는다. 그리고 이렇게 찾은 블록의 첫 번째 바이트를 가리키는 주소값을 반환하게 된다. 힙 영역에 할당할 수 있는 적당한 블록이 없을 때에는 널 포인터를 반환하는데, 주소값을 반환받기 때문에 힙 영역에 할당된 메모리 공간으로 접근하려면 포인터를 사용해야 한다.

free() 함수

free() 함수는 힙 영역에 할당받은 메모리 공간을 다시 운영체제로 반환해주는 함수이다. 데이터 영역이나 스택 영역에 할당되는 메모리의 크기는 컴파일 타임에 결정되어 프로그램이 실행되는 내내 고정되지만 동적할당으로 힙 영역에 생성되는 메모리의 크기는 런 타임 내내 변화된다. 따라서 free() 함수를 사용해 다 사용한 메모리를 해제해 주지 않으면, 메모리가 부족해지는 현상이 발생할 수 있고 이처럼 사용이 끝난 메모리를 해제하지 않아 메모리가 부족해지는 현상을 메모리 누수(memory leak)이라고 한다.

#include <stdlib.h>
void free(void *ptr);

free() 함수는 인수로 해제하고자 하는 메모리 공간을 가리키는 포인터를 전달받는다. 인수의 타입이 void형 포인터로 선언되어 있으므로, 어떠한 타입의 포인터라도 인수로 전달될 수 있다.

ptr_arr = (int*) malloc(arr_len * sizeof(int)); // 메모리의 동적 할당  
if (ptr_arr == NULL) // 메모리의 동적 할당이 실패할 경우
{
    printf("메모리의 동적 할당에 실패했습니다.\n");
    exit(1);
}  
printf("동적으로 할당받은 메모리의 초깃값은 다음과 같습니다.\n");
for (i = 0; i < arr_len; i++)
{
    printf("%d ", ptr_arr[i]);
}
free(ptr_arr);       // 동적으로 할당된 메모리의 반환

calloc() 함수

calloc() 함수는 malloc() 함수와 마찬가지로 힙 영역에 메모리를 동적 할당해주는 함수이며 free() 함수를 통해 할당받은 메모리를 해제해주어야 하는 함수인데, 다른 점은 할당하고자 하는 메모리의 크기를 두 개의 인수로 나누어 전달받는 점이다. 또한, 이 함수는 메모리를 할당받은 후에 해당 메모리의 모든 비트값을 전부 0으로 초기화해준다. 

#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);

calloc() 함수의 첫 번째 인수는 메모리 블록의 개수를 나타내며, 두 번째 인수는 각 블록의 바이트 수를 나타낸다. 따라서 calloc() 함수는 힙 영역에 size 크기의 메모리 블록을 nmemb 개 할당받을 수 있도록 요청한다.

ptr_arr = (int*) malloc(arr_len * sizeof(int));	//밑과 같은 동작 수행
ptr_arr = (int*) calloc(arr_len, sizeof(int));

realloc() 함수

reallocI() 함수는 이미 할당된 메모리의 크기를 바꾸어 재할당할 때 사용하는 함수이다. 

#include <stdlib.h>
void *realloc(void *ptr, size_t size);

realloc() 함수의 첫 번째 인수는 크기를 바꾸고자 하는 메모리 공간을 가리키는 포인터를 전달받는다. 두 번째 인수로는 해당 메모리 공간에 재할당할 크기를 전달한다. 따라서 첫 번째 인수로 NULL이 전달되면, malloc() 함수와 정확히 같은 동작을 하게 된다. 

realloc() 함수는 만약 기존의 메모리 위치에 충분한 공간이 있다면 바로 이어서 추가 메모리 공간을 할당해준다. 하지만 충분한 공간이 없다면, 메모리의 다른 공간에 기존의 데이터를 복사한 후 이어서 추가 메모리 공간을 할당하게 된다.

ptr_arr = (int*) malloc(arr_len * sizeof(int)); // 메모리의 동적 할당

total_len = arr_len + add_len;
ptr_arr = (int*) realloc(ptr_arr, (total_len * sizeof(int))); // 메모리의 추가 할당

'self study > 언어의 기본부터' 카테고리의 다른 글

[c] 구조체의 기본  (0) 2022.11.26
[c] 문자와 문자열  (0) 2022.11.25
[c] 포인터와 배열  (0) 2022.11.22
[c] 포인터  (0) 2022.11.21
[c] 배열  (0) 2022.11.19
Comments