프로그래밍 언어/C

[메모리의 관리] 메모리의 기초 총정리

순정법사 2023.08.17

A. 메모리의 기초 

프로그램이 실행 되기 위해서 먼저 프로그램이 메모리에 로드 되어야 함

또한 프로그램 속 변수를 저장할 메모리도 필요함

따라서 운영체제는 프로그램 실행을 위해 아래와 같은 메모리 공간을 제공

 

1. 메모리의 구조

a.  코드 영역

🌟 실행할 프로그램의 코드가 저장되는 영역 (텍스트 영역)

 

CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리

 

b. 데이터 영역

🌟 프로그램의 전역 변수와 정적(static) 변수가 저장되는 영역

 

프로그램 시작 = 할당 / 프로그램 종료 = 소멸 

 

c. 힙 영역

🌟 프로그래머가 할당( = 동적 할당) / 해제 (자바에서는 가비지 컬렉터가 자동 해제) 하는 메모리 공간 

 

  • 클래스, 클로저가 이 부분에 해당, 런타임 시에 크기가 결정
  • 낮은 주소에서 높은 주소의 방향으로 할당

 

d. 스택 영역

🌟 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 프로그램이 자동으로 사용하는 임시 메모리 영역

 

  • 함수의 호출 = 할당/ 함수의 호출 완료 = 소멸
  • 푸시(push) 동작으로 데이터를 저장하고, 팝(pop) 동작으로 데이터를 인출
  • 후입선출(LIFO, Last-In First-Out) 방식에 따라 동작
  • 높은 주소에서 낮은 주소의 방향으로 할당
  • 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 함

 

 

 

B. 힙 영역

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

a. malloc() 함수

🌟 프로그램이 실행 중일 때 사용자가 직접 힙 영역에 메모리를 할당

 

  • 메모리 크기에 맞고, 아직 할당되지 않은 적당한 블록을 찾아 첫 번째 바이트를 가리키는 주소 값을 반환함
  • 적당한 블록이 없을 때에는 널 포인터를 반환
  • 주소값을 반환하기 때문에 힙 영역에 할당된 메모리 공간으로 접근하려면 포인터를 사용해야 함

 

📘 문법

#include <stdlib.h>

void *malloc(size_t size);

//size : 할당받고자 하는 메모리의 크기를 바이트 단위로 전달
// size_t 타입은 부호없는 정수라고 이해

 

b. free() 함수

🌟 힙 영역에 할당받은 메모리 공간을 다시 운영체제로 반환해 주는 함수

 

데이터 영역이나 스택 영역에 할당되는 메모리의 크기는 컴파일 타임에 결정되어, 프로그램이 실행되는 내내 고정

하지만 메모리의 동적 할당으로 힙 영역에 생성되는 메모리의 크기는 런 타임 내내 변화

따라서 free() 함수로 메모리를 해제해 줘야 함 / 메모리가 부족해지는 현상 = 메모리 누수(memory leak)

 

📘 문법

#include <stdlib.h>

void free(void *ptr);

//*ptr : 해제하고자 하는 메모리 공간을 가리키는 포인터
// void형 포인터로 선언되어 어떠한 타입의 포인터라도 인수로 전달

 

 

🤓 예제 : 크기가 고정된 배열이 아닌 런 타임에 크기가 결정되는 배열 만들기

#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

 

c. calloc() 함수

🌟 힙 영역에 메모리를 동적으로 메모리의 크기를 두 개의 인수로 나누어 전달받아 할당해주는 함수

 

  • 메모리를 할당받은 후에 해당 메모리의 모든 비트값을 전부 0으로 초기화
  • free() 함수를 통해 할당받은 메모리를 해제

 

📘 문법

#include <stdlib.h>

void *calloc(size_t nmemb, size_t size);

//첫 번째 인수 : 메모리 블록의 개수
//두 번째 인수 : 각 블록의 바이트 수

👉 즉, 힙 영역에 size 크기의 메모리 블록을 nmemb개 할당받을 수 있도록 요청

 

🤓 예제 : malloc(), calloc()이같은 동작을 수행하게 함

1. ptr_arr = (int*) malloc(arr_len * sizeof(int));

2. ptr_arr = (int*) calloc(arr_len, sizeof(int));

 

d. realloc() 함수

🌟 이미 할당된 메모리의 크기를 바꾸어 재할당할 때 사용하는 함수

 

  • 첫 번째 인수로 NULL이 전달되면, malloc() 함수와 정확히 같은 동작
  • 기존의 메모리 위치에 충분한 공간이 있다면 바로 이어서 추가 메모리 공간을 할당
  • 기존의 메모리 위치에 충분한 공간이 없으면 메모리의 다른 공간에 기존의 데이터를 복사한 후, 이어서 추가 메모리 공간을 할당

 

📘 문법

#include <stdlib.h>

void *realloc(void *ptr, size_t size);

//첫 번째 인수 : 크기를 바꾸고자 하는 메모리 공간을 가리키는 포인터
//두 번째 인수 : 해당 메모리 공간에 재할당할 크기

 

🤓 예제 : 런 타임에 크기가 결정된 배열의 크기를 realloc() 함수를 사용해 늘리기

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int i, total_len;
	int arr_len = 3, add_len = 2;
	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]);
	}
	
	total_len = arr_len + add_len;
	ptr_arr = (int*) realloc(ptr_arr, (total_len * sizeof(int)));	// 메모리의 추가 할당 
	
	if (ptr_arr == NULL)	// 메모리의 추가 할당에 실패할 경우
	{
		printf("메모리의 추가 할당에 실패했습니다.\n");
		exit(1);
	}
			
	printf("\n추가로 할당받은 메모리의 초기값은 다음과 같습니다.\n");
	for (i = 0; i < total_len; i++)
	{
		printf("%d ", ptr_arr[i]);
	}
	
	free(ptr_arr);			// 동적으로 할당된 메모리의 반환 
	
	return 0;
}

✨ 실행결과

동적으로 할당받은 메모리의 초기값은 다음과 같습니다.
0 0 0 

추가로 할당받은 메모리의 초기값은 다음과 같습니다.
0 0 0 0 0

 

 

 

C. 스택 영역

1. 스택 프레임 (stack frame)

a. 스택 프레임의 정의

🌟 스택 영역에 차례대로 저장되는 함수의 호출 정보(함수의 매개변수, 호출이 끝난 뒤 돌아갈 반환 주소값, 함수에서 선언된 지역 변수 등)

 

스택 프레임 덕분에 함수의 호출이 모두 끝난 뒤에, 함수 호출 이전 상태로 돌아갈 수 있음

 

b. 스택 프레임의 동작 방식

🤓 기본 예제

int main(void)
{
    func1();  // func1() 호출
    return 0;
}

void func1()
{
    func2();  // func2() 호출
}

void func2()
{
}

 

🎨 도식화

 

후입선출, push pop 으로 데이터가 저장되고 삭제됨

 

c. 스택 오버플로우 (stack overflow)

🌟 함수의 재귀 호출이 무한히 반복되어서 스택의 모든 공간을 다 차지하고 난 후 더 이상의 여유 공간이 없을 때 또 다시 스택 프레임을 저장하게 되면, 해당 데이터는 스택 영역을 넘어가서 저장되는 것

 

🎨 도식화

 

 

💥 스택 오버플로우가 되면 오동작을 하거나 취약점을 가지게 되므로 에러를 발생시키고 강제 종료 시킴

 

 

 


출처 : http://www.tcpschool.com/c/c_memory_structure

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com