강의/computer science

[ComputerScience] Memory Model, Stack VS Heap, Memory Operation

하기싫지만어떡해해야지 2024. 9. 6. 14:37

이 게시글은

서울대학교 데이터사이언스 대학원

조요한 교수님의

데이터사이언스 응용을 위한 컴퓨팅 강의를

학습을 목적으로 재구성하였습니다


Memory Model

메모리 모델이란

프로그래밍 언어에서

메모리를 관리하는 방식이다

 

아마 언어마다 각각의 메모리 모델이

다르겠지만, 이번 수업에서는

크게 python과 C언어에 대해서만

비교를 했다

 

 

python의 Memory Model

python의 메모리 모델은

high-level

comceptual diagram이다

 

파이썬은 

 

위 다이어그램처럼 메모리를 관리하는데,

frame 한 개는

한 개의 scope이라고 보면 된다

 

python은 변수들의 정확한 memory location은

숨겨져있으며, 

reference 형식으로 메모리를 참조한다

 

 

C언어의 Memory Model

사실 중요한 것은

c언어의 메모리 모델이다

 

c언어의 메모리 모델은

low-level

concrete diagram이다

 

저 오른쪽의 다이어그램이

굉장히 중요하다고 한다

c언어의 메모리 모델에는

4가지 섹션이 있는데

이 각각의 섹션에는

섹션의 용도에 맞게 데이터들이 저장된다

 

1) text section(program section)

c언어로 프로그램을 작성할 때

실제 코드가 저장되는 부분이다

 

2) global section

이 부분에는 흔히 전역변수라고 하는

글로벌 변수들이 저장된다

프로그램이 어디에서나 접근할 수 있는

변수들을 저장하는 부분이다

 

3)heap

dynamic하게 메모리를 할당할 때 저장한다

이 dynamic 하다는게 무슨 뜻이냐면

프로그래밍을 하다보면

변수를 할당하여 사용하거나

변수 사용이 끝나서 할당을 해제해야할 경우가 있는데

이러한 변수들을 담는 공간이다

 

프로그램에서 필요할 때 마다 할당할 때

heap에 데이터들을 할당한다

 

4) runtime stack

이 부분은 유저나 개발자가

마음대로 할당할 수 없는 부분이다

컴파일러가 코드를 보고 계산해서 

변수를 저장하거나 할 때 할당하는 부분이다

 

자료구조의 stack처럼

데이터들이 할당되어서

rutnime stack이라고 부른다

 


Stack VS Heap

 

그렇다면 stack과 heap을 비교해보자

 

Stack(runtime stack)

stack에는 메모리가 할당될수록 낮은 address가 배정된다

현대의 아키텍처들은 모두 이런 방식이라고 한다

 

스택에 메모리를 얼만큼 할당할지는 컴파일러가 계산하는 것이며

개발자가 설정할 수 없다

 

주로 runtime stack에는 함수 내부의 변수같은

local variables들이 할당되며

컴파일러가 미리 계산을 해놓기 때문에

빠르고 효율적인 메모리 관리가 가능하다

 

빠르고 효율적인 메모리관리가 가능하다는 뜻은

연속적인 공간관리가 가능하다는 뜻인데,

fragment(단편화)

즉, 할당된 데이터와 데이터들 사이에

빈 공간이 없이 연속적으로

메모리 효율을 최대화하며

연속적으로 할당된다는 뜻이다

 

Heap

동적으로 데이터를 할당한다

동적으로 데이터를 할당한다는 뜻은,

개발자가 필요할 때마다 메모리를 할당하도록

설정할 수 있다는 뜻이다

 

프로그래머가 스스로 필요한 메모리를

allocation(할당)하고 사용이 끝났으면

deallocation(해제)할 수 있다

 

heap에 들어있는 데이터에 접근하기 위해서는

pointer변수를 사용한다

 

이러한 heap은 stack에 비해서

상대적으로 느리고 단편화(fragmented)되어 있고

데이터가 연속적으로 저장되지 않는다

 

이는 위의 stack과는 반대로

할당된 데이터와 데이터 사이에 빈 공간이 생겨

메모리를 효율적으로 관리하지못할 가능성이

크다는 뜻인데,

이러한 이유는 프로그래머가 동적으로 할당하는 부분은

어디에 어떤 데이터가 있다가 언제 사라질지

예측하기가 어렵기 때문에

중간중간에 불가피하게 낭비되는 메모리 공간이

생길 수 밖에 없는 것이다

 

C언어에서 heap에 데이터를 동적으로 할당할 때

사용하는 함수는

malloc function이며

int *ptr = malloc(sizeof(int));

와 같은 방식으로 할당한다

위 코드는 int 데이터를 가진

ptr이라는 포인터 변수의 주소값에

int 크기만큼의 메모리를 할당한다는 뜻이다

 

heap에 데이터를 동적으로 해제할 때

사용하는 함수는

free function이다

데이터가 메모리를 다 사용하고

더이상 해당 데이터를 사용할 일이 없을 때

free(ptr);을 통해서

ptr변수에 할당된 메모리 공간만큼이 해제되며

그 공간을 다른 데이터들을 위해 사용할 수 있게된다

 

heap에 메모리가 할당이 한 번 되면,

프로그램이 끝나거나 개발자가 해제할 때까지 계속 할당이 되어있어

메모리 공간 사용이 끝난 후에도 할당 해제를 해주지 않는다면

그 메모리 공간만큼은 프로그램이 끝날 때까지

이용을 할 수 없게되어 메모리 공간의 낭비가 발생한다

 

Memory Operation

그렇다면 c언어에서

여러 함수들을 call할 때

컴파일러가 어떤 식으로 데이터들을

runtime stack에 할당하고 할당을 해제하는지

구체적으로 알아보도록 하자

 

 

이런 c언어 코드를

컴퓨터에서 실행시켰다고 가정해보자

 

이 프로그램을 실행하면서

c언어는 어떻게 메모리들을

할당하고 해제할까?

 

우선 모두가 알겠지만

c언어에서 가장 먼저 실행되는 부분은

main 함수이다

프로그램의 entry 부분이라고 할 수 있다

 

가장 처음 main함수가 실행되면서

runtime stack에는

main함수를 위한 메모리가 할당된다

 

stack point는 지금 실행되고 있는 함수의 메모리 중

가장 윗 단을 나타내고

frame point는 가장 아랫단을 나타낸다

 

메모리의 젤 처음 부분인 stack point와

메모리의 젤 마지막 부분인 frame point로

현재 어디서부터 어디까지가

이 function을 위해 할당되어있는지를

tracking한다

 

이제 main함수의 

b = Watt(a); 부분에서

Watt함수가 실행됐다

 

그렇다면 당연히 현재 실행 중인

Watt 함수에 대한 메모리 공간도

할당될 것이다

 

 

main이 할당된 메모리 위에

Watt 함수에 대한 메모리 부분이

할당되었다

 

이렇게 차곡차곡 쌓이는 자료구조를

stack이라고 부르며

stack에서 새로운 데이터가 들어가는걸

push됐다고 부른다

 

이 할당된 Watt 메모리 안에는

watt 함수의 실행이 끝났을 때

어디로 메모리가 돌아가야할지,

인자값은 어디에 있는지,

watt라는 함수안에서 새로 정의한 변수 w를 위한 공간도

watt영역 안에 저장된다

 

이 안에 저장되는 메모리는

watt라는 함수를 정상적으로 실행시키기위한

데이터들이 저장된다고 보면 된다

 

 

이제 실행되고 있는 Watt함수 안에서 

Volt라는 함수가 호출되었다

 

그럼 아까 Watt와 마찬가지로

Volt 함수를 위한 메모리 공간도

Watt위에 stack 형식으로 push 될 것이다

 

이렇게 Volt함수까지 모두 실행되고

Volt함수의 실행이 끝났다

 

그럼 이제 Volt함수에 대한 메모리 공간은

필요가 없어지므로 할당을 해제해줘야된다

 

Volt함수의 실행이 끝나면

Volt함수에 대한 메모리 공간이 할당 해제된다

 

이러한 방식으로 데이터가 빠져나가는걸

stack구조에서는 pop되었다고 부른다

 

이렇게 Volt가 pop되면 Watt함수의

마지막을 가리키도록 포인터가 변경된다

 

그렇게 Watt함수의 실행도 끝나면

Watt함수의 메모리 공간도

할당이 해제되어 pop된걸 볼 수 있다

 

C언어에서 컴파일러는 이러한 방식으로

runtime stack에 메모리를 할당하고 해제한다


 

학생 질문 정리

Q) #include <stdio.h>와 같은 헤더파일에 대한 메모리는 어떻게 할당하고 해제하나요?

A) 메모리 모델에 program section이라고 소스코드를 저장하는 부분이 따로 있음

모든 코드들은 다 그곳에 저장됨

runtime stack은 실제로 실행을 하면서 변수들이 저장되는 곳임

header에서 호출한 부분이 현재 사용되고있지 않다면 runtime stack에는 저장되지 않음

만약 header파일이 호출되는 부분이 있으면 그때 runtime stack에 저장됨

 

 

Q) python은 어떤 방식으로 메모리를 관리하는지?

A) 큰 레벨에서 보면 c언어와 다르지 않으나, 파이썬은 이러한 메모리 할당 해제 과정을

stack이 아닌 heap에서 다루는걸로 알고있음

C언어 같은 경우는 코드를 미리 읽어서 파악하기 때문에 이런 방식의 할당이 가능하지만,

python같은 경우는 코드 전체를 미리 보지 않기 때문에 heap을 사용해서 동적으로 변수를 저장함