/C
📌 메모리의 구조
- 프로그램이 운영체제로부터 할당받는 대표적인 메모리 공간에는 코드, 데이터, 스택, 힙 영역이 있다.
- 코드 영역: 실행할 프로그램의 코드가 저장되는 영역이다. 텍스트 영역이라고도 한다.
- 데이터 영역: 전역 변수, 정적(static) 변수를 저장한다. 프로그램 시작 시 할당되고, 종료 시 소멸된다.
- 스택 영역: 지역변수와 매개변수를 저장한다. 함수의 호출과 함께 할당되고, 호출 완료시 소멸된다. 이 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임이라고 부른다. 스택 영역에서는 푸시(push)로 데이터를 저장하고, 팝(pop)으로 데이터를 인출하는 후입선출(FILO) 방식으로 데이터가 관리된다. 메모리의 높은 주소에서 낮은 주소 순으로 할당된다.
- 힙 영역: 사용자가 직접 관리하는 메모리 영역이다. 즉, 메모리의 동적 할당이 일어나는 영역이며 낮은 주소에서 높은 주소 순으로 할당된다.
📌 스택 프레임
- 함수가 호출되면 스택 영역에 매개변수, 호출이 끝난 뒤 돌아갈 반환 주소값, 함수에서 선언된 지역변수 등이 저장된다. 이렇게 스택에 저장되는 함수의 호출 정보를 스택 프레임이라고 한다.
- 스택 프레임의 동작 방식을 살펴보자.
int main(void)
{
func1(); // func1() 호출
return 0;
}
void func1()
{
func2(); // func2() 호출
}
void func2()
{
}
이러한 함수가 있다고 했을 때,
위 그림과 같은 후입선출(FILO) 방식으로 메모리가 할당되고 동작한다.
- 위의 예시에서 함수의 재귀호출을 무한히 반복하면 해당 프로그램은 스택 오버플로우에 의해 종료된다.
재귀호출이 반복되면 스택 프레임이 계속 쌓이게 된다. 이렇게 쌓인 스택 프레임이 스택 영역을 초과하게 되면 오작동을 일으키거나 보안상 취약점이 될 수 있기 때문에 스택 오버플로우가 발생하여 에러를 일으키고 곧바로 프로그램을 강제 종료시킨다.
📌 메모리의 동적 할당
- 데이터/스택 영역에 할당되는 메모리 크기는 컴파일 타임에 결정된다. 하지만 힙 영역은 런타임에 사용자가 직접 메모리 크기를 결정하여 할당한다. 이렇게 런타임에 메모리가 할당되는 것을 메모리의 동적 할당(dynamic allocation)이라고 부른다.
- malloc() 함수: 프로그램이 실행중일 때 사용자가 직접 힙 영역에 메모리를 할당하게 하는 함수이다.
//원형
#include <stdlib.h>
void *malloc (size_t size);
할당받고자 하는 메모리의 크기를 바이트 단위로 전달받은 후, 적합한 메모리 블록을 찾아 주소값을 반환한다. 이때 적합한 메모리 블록이 없다면 널 포인터를 반환하게 되고, 주소값을 반환하기에 여기에 접근하려면 포인터를 사용해야 한다. malloc함수의 원형에서 사용된 size_t는 부호가 없는 정수 타입이라고 보면 된다.
- free() 함수: 힙 영역에 할당받은 메모리 공간을 운영체제에 반환해주는 함수이다. 이를 메모리 해제라고 한다.
//원형
#include <stdlib.h>
void free(void *ptr);
인수의 타입이 void형 포인터로 선언되어 있으므로, 어떠한 타입의 포인터라도 인수로 전달될 수 있다. 또한 사용이 끝난 메모리를 free 함수로 해제하지 않으면 메모리 부족 현상이 발생할 수도 있다. 이를 메모리 누수라고 한다.
예시 코드를 살펴보자.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i;
int arr_len = 3;
int* ptr_arr;
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); // 동적으로 할당된 메모리의 반환
return 0;
}
//동적으로 할당받은 메모리의 초기값은 다음과 같습니다.
//0 0 0
- calloc() 함수: calloc() 함수는 malloc() 함수와 마찬가지로 힙 영역에 메모리를 동적으로 할당해 주는 함수이다. malloc() 함수와 다른 점은 할당하고자 하는 메모리의 크기를 두 개의 인수로 나누어 전달받는 점이다. 또한, calloc() 함수는 메모리를 할당받은 후에 해당 메모리의 모든 비트값을 전부 0으로 초기화해 준다.
//원형
#include <stdlib.h>
void *calloc(size_t nmemb, size_t size);
calloc() 함수도 malloc() 함수와 마찬가지로 free() 함수를 통해 할당받은 메모리를 해제해 주어야 한다.
- realloc() 함수: 이미 할당된 메모리의 크기를 바꾸어 재할당할 때 사용한다.
//원형
#include <stdlib.h>
void *realloc(void *ptr, size_t size);