3 분 소요


메모리

  • 일반적으로 ‘메모리’ 라고 하면 컴퓨터 구조에서 RAM을 지칭합니다.
  • 우리가 컴퓨터에서 응용프로그램을 실행시키면 아래와 같은 과정이 진행됩니다.
    1. exe 파일 (실행 파일) 을 실행하도록 운영체제에 요청한다.
    2. 운영체제는 메모리에 프로그램을 로드한다. (프로그램에 정보를 읽고 메모리에 공간을 할당)
    3. 프로그램의 코드 (변수, 함수) 를 메모리에 읽고 쓰면서 동작한다.





메모리 구조

  • ‘프로그램이 실행된다’ 는 것은 2가지 의미를 가지고 있습니다.
    1. 실행 파일이 메모리에 적재된다.
    2. CPU를 할당 받고 명령을 수행하고 있는 상태이다.


오늘은 '실행 파일이 메모리에 적재된다' 에 집중해 보겠습니다.


프로그램이 운영체제로부터 할당 받는 대표적인 메모리 공간은 다음과 같습니다.

프로그램 마다 각각 다른 메모리 공간에 할당하게 됩니다.


Code 영역

- 코드(code) 영역은 실행할 프로그램의 코드가 저장되는 영역으로 텍스트(Text) 영역이라고도 부릅니다.
-  명령문들이 저장되며 함수, 제어문, 상수 등이 코드 영역에 저장됩니다.
-  CPU는 코드 영역에 저장된 명령어를 하나씩 가져가서 처리합니다.


Data 영역

- 데이터(data) 영역은 프로그램의 전역 변수와 정적(static) 변수가 저장되는 영역입니다.
- 저장되는 변수의 특징을 보면 프로그램의 시작 (main 함수 실행 전)과 함께 할당되며, 프로그램이 종료되면 소멸한다.

Heap 영역

- 힙(heap) 영역은 사용자가 직접 관리할 수 있는 '그리고 해야만 하는' 메모리 영역입니다.
- 힙 영역은 사용자에 의해 메모리 공간이 동적으로 할당되고 해제됩니다.
- 힙 영역은 메모리의 낮은 주소에서 높은 주소의 방향으로 할당됩니다.

Stack 영역

- 메모리의 스택(stack) 영역은 함수의 호출과 관계되는 지역 변수와 매개변수가 저장되는 영역입니다.
- 스택 영역은 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸합니다.

프로그램에서 함수 호출이 발생하면, 해당 함수 실행에 필요한 매개변수, 지역변수 등의 정보가 저장되는 메모리 영역을 스택(Stack)이라고 합니다.

스택 프레임

스택 프레임(Stack Frame)은 이러한 스택 메모리 영역에서 함수 호출에 대한 정보를 저장하는 논리적인 단위입니다.(Stack 영역은 1개, Stack Framedms 여러 개 생성 가능)

스택 프레임에는 함수 호출 시점에 다음과 같은 정보들이 저장됩니다.

  • 함수의 매개변수 값
  • 지역 변수 및 임시 변수
  • 반환 주소 (리턴값을 저장할 메모리 주소)
  • 이전 스택 프레임의 포인터

함수가 호출될 때마다 새로운 스택 프레임이 생성되며, 함수가 종료될 때마다 스택 프레임은 제거됩니다. 이러한 스택 프레임의 생성과 제거로 인해 프로그램 실행 중에 스택 메모리 영역이 계속해서 변화하게 됩니다.

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

// 스택 프레임 예시 코드

#include<iostream>
using namespace std;

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

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

void func2()
{

}



스택 오버플로우

스택 오버플로우(Stack Overflow)란, 프로그램에서 스택 메모리 영역에 할당된 공간을 넘어서 데이터가 쓰여지는 상황을 말합니다.

스택 메모리 영역은 한정된 크기의 메모리 공간을 가지므로, 함수 호출이 발생할 때마다 스택 프레임이 생성되고 제거되어야 합니다. 그러나 만약 함수 호출 시, 스택 메모리 영역에 할당된 공간을 초과하여 데이터를 저장하려고 한다면, 스택 오버플로우가 발생하게 됩니다.





포인터란?

포인터의 크기는 운영체제마다 다르다?

32bit 운영체제란 컴퓨터의 데이터 기본 처리 단위가 32bit 라는 의미입니다. 즉, 레지스터에 한번에 저장할 수 있는 데이터의 양이 32bit 라는 의미입니다.

CPU가 코드 영역을 읽어 내려가다 A + B 라는 명령어를 만났다고 가정해봅시다.

  1. CPU의 레지스터1 에서 A가 저장된 메모리 주소를 찾아 저장하고 레지스터2에서 B가 저장된 메모리 주소를 찾아 저장합니다.

  2. 값을 다 찾았다면 CPU가 레지스터1 과 2를 + 연산합니다.

여기 주목해야할 점은 레지스터에 메모리 주소가 저장된다는 것입니다.

포인터 또한 메모리 주소를 저장하는 변수이기 때문에 32bit 운영체제에서는 포인터 변수의 크기가 4byte(32bit), 64bit 운영체제에서는 8byte(64bit)가 되는 것입니다.


더블 포인터의 동작 과정

  • doublePointer = pointer의 주소
  • *doublePointer = pointer의 값 = i의 주소
  • **doublePointer = pointer가 참조하는 값 = i의 값





동적 할당

동적 할당이란?

  • 동적 메모리 할당이란 실행시간(Run-time) 에 사용할 메모리 공간을 할당하는 것으로 힙(Heap) 영역에 저장됩니다.
  • C에서는 ‘malloc 함수’ 를 C++ 에서는 ‘new 연산자’ 를 제공합니다. (C++에서도 malloc 을 제공한다.)

C++ 에서는 반드시 객체의 생성자 및 소멸자를 호출할 수 있는 new / delete 연산자를 사용해야 합니다.


동적 할당을 사용하는 이유

  1. 한정적인 메모리를 효율적으로 사용
  2. 유효 범위 이후에 데이터 보존


malloc과 free

new과 delete


new와 molloc 의 차이

구분 설명
생성자 호출 유무 new 연산자는 객체의 생성자를 호출하지만 malloc 함수는 호출하지 않는다.
연산자 vs 함수 new는 연산자이고 malloc은 함수이다.
반환 자료형 new 연산자는 반환 자료형은 선언한 자료형 이지만 malloc은 void*이다. 둘 다 반환 값은 할당된 메모리의 포인터이다.
할당에 실패 한 경우 new 연산자는 bad_alloc를 예외로 던지고 프로그램이 종료되지만 malloc은 Null 값을 반환한다.
크기 계산 new 연산자는 컴파일러에 의해 자동으로 계산되지만 malloc은 필요한 바이트 수를 직접 계산해주어야 한다.


메모리 동적 할당 시 주의점

  • 동적할당으로 할당한 메모리를 스마트 포인터가 아닌 일반 포일터로 저장한 경우 반드시 해제를 해주어야한다.
    • 동적할당한 메모리를 해제하지 않으면 메모리 누수 (Memory Leak) 가 발생한다.
  • 메모리를 해제한 포인터는 해제 되었음을 확인할 수 있게 반드시 nullptr 로 초기화 해주어야 한다.
    • 조건문을 통해 포인터 변수가 nullptr 인 것을 확인하여 재할당을 막을 수 있다.

메모리 누수

  • 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상


메모리 동적 할당에 실패한 경우

malloc의 경우

  • 반환 값
    • malloc은 할당된 공간에 대한 void* 를 반환하거나 사용 메모리가 부족한 경우 NULL을 반환합니다.
    • void 포인터가 아닌 형식에 대한 포인터를 반환하려면 반환 값에 형식 캐스팅을 사용하세요.

new의 경우

  • 반환 값
    • 할당 요청에 new 연산자에 대한 메모리가 부족한 경우 std::bad_alloc 예외를 던집니다.
    • new(std::nothrow) 를 사용한 경우에는 nullptr 을 반환합니다.

댓글남기기