[OS] Stack은 컴파일 시점과 런타임 시점 중, 언제 결정되는가?

 

 

OS 메모리 구조를 다시 봤다.

찬찬히 Stack과 Heap 차이점을 보다가 생긴 의문점이 제목과 같아 차근히 서술해보려고한다.

 

 

1️⃣ Stack과 Heap, 각각에 대하여


Stack의 크기 결정

  • 컴파일 시점에 최대 크기에 대한 결정이 이루어질 수 있다
  • LIFO(Last In, First Out) 방식으로 운영되며, 데이터는 함수 호출과 종료에 따라 자동으로 할당되고 해제된다.

Heap의 크기 결정

  • 런타임 시점에 프로그래머에 의해 동적으로 설정된다

 

2️⃣ Stack과 Heap의 비교


✔ Stack & Heap 공통점

  • 컴퓨터 자체 리소스 크기 영향을 크게 받는다

🧚‍♂️ 음.. 현대의 컴퓨터는 감당할 만큼 커지지 않았나요?

현대에서는 당연히 매우 큰 메모리를 지원하고 있다. 하지만 사용 가능한 물리적 제한이라는 것은 존재하며, 가상 메모리에 의한 제한도 존재한다. 무한이란 건 없다.

✔ Stack & Heap 차이점

  • Stack은 함수나 지역변수가 저장되어 있으며, Heap은 클래스와 같은 참조 타입 데이터가 저장된다
  • Stack은 OS와 Linker의 관리를 받지만, Heap은 개발자가 필요에 따라 메모리를 동적 관리한다
  • 즉, Stack은 운영체제가 자동으로 관리하며, Heap은 개발자가 수동으로 관리한다

 

3️⃣ Stack과 Heap의 접근 속도 차이


✔ 접근 속도 측면

Stack이 Heap보다 접근 속도가 빠르다. 그 이유는 Stack 자체가 프로그램의 실행을 관리하기 위해 '예약'된 메모리 영역이기 때문이다. 이 실행을 위하여 접근 방식 자체가 매우 빠르고 효율적인 방향으로 설계되어 있다

🧚‍♂️ 그 효율적인 방법이 뭔가요?

Stack에 메모리 할당 시, 운영체제는 새 할당을 위하여 Pointer를 사용한다
이를 통해 동적 메모리 관리가 필요하지 않은, 그저 주소값을 가리키는 곳의 변화를 통하여 빠르게 작업이 진행된다.

🧚‍♂️ Heap의 접근 속도는 왜 상대적으로 느린 걸까요?

Heap은 Stack에 비하여 더 복잡한 연산과 과정을 반복한다.
프로그램의 OS로부터 메모리 요청을 받은 후, 그 메모리를 스스로 관리하고 메모리를 해제하는 작업을 계속해야 한다. 가령, C++에서는 'new' 연산자를 통해 메모리 주소가 포인터 변수에 저장되었다가, 메모리 해제는 'delete' 연산자를 사용하여 수행한다. 이 과정이 자동으로 관리되지 않고 명시적으로 할당 및 해제한다는 점에서 느려진다.

🧚‍♂️ Java에서도 프로그래머가 명시적으로 관리하나요?

JVM의 큰 특징인 Garbage Collection은 이를 자동으로 관리한다. 가비지 컬랙션은 JVM에 의해 관리되며, 특정 시점(메모리 할당 요청이 있을 때와 같이)에 자동으로 실행된다. 객체가 더 이상 필요 없게 됐을 때, 즉 어떤 참조 변수도 객체를 가리키지 않을 때 가비지 컬렉터에 의해 메모리가 회수된다. 

 

4️⃣ 헷갈렸던 것, 지역변수가 참조 타입일 수 있잖아.


Stack은 함수나 지역변수가 저장되며, Heap은 참조 타입 객체가 저장된다고 했다. 하지만 지역변수에는 참조 타입 객체가 될 수도 있는 건데, 정확히 구분을 어떻게 지어야 하는 걸까?

MyClass obj = new MyClass;

간단하게도, 이 소스 코드로 이해할 수 있다.

  • obj는 Stack에 저장된다
  • new MyClass는 Heap에 저장된다

즉, 실제 인스턴스는 Heap에 저장되고 객체를 가리키는 참조만이 Stack에 저장되는 것이다. 이러한 메커니즘으로 메모리 관리의 효율성과 프로그램의 유연성이 높아지게 된다.

4️⃣ 실은 더 헷갈리는 것, Stack이 컴파일 시점에 결정된다고?


어디서는 Stack의 크기는 '컴파일 시점에 결정된다'라고 하며, 어디서는 '런타임에 동적으로 결정된다'라는 대립한 두 말이 존재한다. 이는 간단하게 오해를 풀 수 있다.

'Stack의 크기는 런타임에 동적으로 결정된다'라는 말은 컴파일 시 고정된 크기로 설정되는 것을 부정하는 게 아니다. 단, 실제로 사용되는 메모리양이 함수의 호출과 종료에 따라 동적으로 변한다는 것을 의미한다. 즉, 메모리 크기가 런타임에 확장되거나 축소되지는 않지만, 실제 사용되는 Stack의 양이 런타임에 변경된다는 것이다.