Fall in IT.

프로그램 메모리 구조의 이해 본문

컴퓨터 & 네트워크

프로그램 메모리 구조의 이해

D.Y 2025. 10. 29. 20:27
반응형

개발 과정에서 '스택 오버플로우'나 '메모리 누수'와 같은 용어를 빈번하게 접하게 된다. 프로그램이 실행될 때 코드, 변수, 함수 등이 저장되는 메모리 공간의 구조를 이해하는 것은 중요하다.

본 글은 프로그램 실행 시 사용되는 메모리 공간의 구조를 분석하고, 개발자가 이를 이해해야 하는 이유를 예제 코드와 함께 기술한다.

1. 코드(Code) 영역

코드 영역은 작성된 소스 코드가 컴파일된 기계어(Machine Code)가 저장되는 공간이다. 이 영역은 프로그램 시작 시 메모리에 로드되어 프로그램이 종료될 때까지 유지된다.

  • 특징: 실행 중 코드 변경을 방지하여 프로그램의 안정성을 확보하기 위해 읽기 전용(Read-Only)으로 설정되는 경우가 많다. CPU는 이 영역의 명령어를 순차적으로 읽어 실행한다.
  • 메모리 크기: 컴파일 타임에 크기가 결정되며, 실행 중에는 변하지 않는다.
// 이 함수의 기계어 코드가 '코드 영역'에 저장됩니다.
func add(x, y int) int {
	return x + y
}

2. 데이터(Data) 영역

데이터 영역은 전역 변수(Global variables)와 정적 변수(Static variables)가 저장되는 공간이다. 이 변수들은 프로그램 내의 모든 함수에서 접근 가능하며, 프로그램 시작과 함께 메모리에 할당되었다가 프로그램 종료 시 소멸한다.

  • 특징: 프로그램 실행 동안 지속적으로 유지되어야 할 데이터(예: 프로그램의 환경 설정 정보, 전역 카운터)를 저장한다.
  • 메모리 크기: 컴파일 타임에 크기가 결정된다.
// 전역 변수: 프로그램 전체에서 접근 가능하며, 데이터 영역에 저장됩니다.
var globalCounter int = 0

// 상수: 상수는 보통 코드 영역 또는 데이터 영역에 저장됩니다.
const MaxSize = 1024

// 이 함수는 데이터 영역에 있는 globalCounter 값을 변경합니다.
func incrementCounter() {
	globalCounter++
}

3. 힙(Heap) 영역

힙 영역은 프로그램 실행 시간(런타임)에 개발자의 필요에 따라 메모리를 동적으로 할당하고 해제하는 공간이다. 데이터의 크기가 불확실하거나, 함수의 생명주기보다 더 오래 유지되어야 할 데이터를 저장할 때 사용된다.

  • 특징: 스택보다 상대적으로 속도가 느리나, 크기 제한이 거의 없어 매우 큰 데이터를 다룰 수 있다. Go, Java, Python과 같이 가비지 컬렉터(GC)가 있는 언어는 사용이 끝난 메모리를 자동으로 정리하는 반면, C/C++ 등은 개발자가 직접 해제(free, delete)해야 한다.
  • 관리 방식: 힙은 특정 자료구조(FIFO, LIFO)를 따르지 않고, 비어있는 공간에 데이터를 할당하는 방식으로 동작한다.
// Go 언어에서 동적으로 생성되는 데이터는 대부분 힙 영역을 사용합니다.
func heapExample() {
	// 슬라이스, 채널, 맵 등 복합 타입은 힙에 데이터를 저장합니다.
	slice := make([]int, 1000)
	channel := make(chan int, 100)
	mapData := make(map[string]int)

	// new 키워드로 포인터를 할당하는 경우
	ptr := new(int)
	*ptr = 100

	// 구조체 포인터 역시 힙에 할당됩니다.
	person := &Person{Name: "Kaye", Age: 36}
}

4. 스택(Stack) 영역

스택 영역은 함수의 호출과 관련된 정보(매개변수, 지역 변수, 리턴 주소 등)를 저장하는 임시 메모리 공간이다. 함수가 호출될 때 스택 프레임(Stack Frame)이 생성되며, 함수 실행이 종료되면 해당 스택 프레임은 자동으로 소멸한다.

  • 특징: 후입선출(LIFO, Last-In First-Out) 구조로, 가장 나중에 입력된 데이터(가장 최근에 호출된 함수)가 가장 먼저 처리된다. 메모리 할당과 해제 속도가 매우 빠르지만, 미리 정해진 크기 제한(통상 1~8MB)이 있다는 단점이 있다.
func stackExample(param1 int, param2 string) { // 1. 매개변수들이 스택에 저장됩니다.

	// 2. 지역 변수들도 스택에 저장됩니다.
	localInt := 100
	localArray := [10]int{1, 2, 3, 4}

	// 3. 다른 함수 호출 시, 새로운 스택 프레임이 이 위에 쌓입니다.
	anotherFunction(localInt)

} // 4. 함수가 종료되면, 이 함수를 위해 사용된 모든 스택 메모리가 자동으로 해제됩니다.

오버플로우(Overflow)의 개념

오버플로우는 한정된 메모리 공간이 부족하여 데이터가 넘쳐흐르는 현상을 의미한다.

메모리 구조는 일반적으로 [코드, 데이터, 힙, 스택] 순으로 배치되는데, 힙은 낮은 주소에서 높은 주소로, 스택은 높은 주소에서 낮은 주소로 확장된다. 이 두 영역이 서로의 공간을 침범하면 오버플로우가 발생하며, 이는 프로그램의 비정상적 종료로 이어질 수 있다.

  • 스택 오버플로우: 재귀 함수가 무한히 호출되거나 함수 내에서 과도하게 큰 지역 변수를 선언할 때 발생한다. 스택 영역이 할당된 크기를 넘어 힙 영역을 침범하는 경우이다.
  • 힙 오버플로우: 힙에 할당된 메모리 공간을 초과하여 데이터를 쓰려고 할 때 발생하며, 주로 보안 취약점으로 이어질 수 있다.

메모리 구조 이해의 중요성

개발자의 메모리 구조에 대한 이해는 더 효율적인 코드 작성을 위한 필수적인 요소이다. 그 이유는 다음과 같다.

  • 성능 최적화 스택은 힙보다 속도가 빠르다. 데이터의 크기와 생명주기를 고려하여 적절한 위치에 데이터를 할당함으로써 프로그램 성능을 향상시킬 수 있다.
  • 메모리 누수 방지 힙에 할당된 메모리가 정상적으로 해제되지 않으면 메모리 누수(Memory Leak)가 발생하여 시스템 전반에 영향을 미칠 수 있다. (단, GC는 이 문제의 상당 부분을 해결한다.)
  • 안정적인 프로그램 스택 오버플로우의 원인을 이해하면 무한 재귀와 같은 치명적인 버그를 사전에 방지할 수 있다.
  • 올바른 자료구조 선택 데이터의 특성에 맞춰 변수의 저장 위치(전역, 지역, 동적 할당)를 결정하는 데 도움이 된다.

결론적으로, 메모리 구조에 대한 이해는 더 효율적이고 안정적인 프로그램을 설계하는 기반이 된다.

반응형
Comments