본문 바로가기
cs공부/운영체제

운영체제 - 스택 메모리, 힙 메모리 자세히 알아보기 +) args란

by 코딩하는 돼징 2024. 5. 15.
반응형

메모리 구조에 대해 알아보는데 문서로만 읽고서는 잘 이해가 가지 않아 실제 코드로 이해하고 싶어 게시글을 쓰게 되었다.

 

우리가 흔히 메모리의 구조에 대해 구글링 해보면 아래와 같은 형식이 나온다.

01 코드 영역

텍스트 영역이라고도 불린다. 기계어로 이루어진 명령어가 저장된다. 코드 영역에는 데이터가 아닌 CPU가 실행할 명령어가 담기기 때문에 쓰기가 금지된 read-only공간이다. 실행되는 프로그램의 코드가 저장되는 메모리 영역

02 데이터 영역

프로그램이 실행되는 동안 유지되어야 하는 데이터가 저장되는 공간이다. 대표적으로 전역변수와 정적 변수 등이 이영역에 해당된다. 이 변수들은 프로그램이 시작될때 할당되고 프로그램 종료시 소멸된다.

03 힙 영역

프로그래머가 직접 공간을 할당/해제하는 메모리 영역이다. 프로그램 동작(런타임)시에 크기가 결정된다. 힙 영역에서 할당된 메모리는 프로그래머가 명시적으로 반환해주어야 하며 반환하지 않는 경우 메모리 누수가 발생할 수 있다. 런타임시에 크기가 결정한다. 

04 스택 영역

함수나 메서드의 지역 변수와 매개 변수가 저장된다. 함수나 메서드가 호출될 때마다 스택프레임에 쌓인다.


이렇게 글로만 보면 이해하기 어려웠다. 그래서 실제 코드를 통해서 알아보자

Stack 메모리

class codepiggy
{
    public static void Main(string[] args)
    {
        int a = 7;
        int b = 3;
        int c = a + b;
    }
}

Stack메모리에서 매개변수란 함수를 실행시킬때 필요한 변수(args)이고 지역 변수란 함수 바디안에 있는 변수(a,b,c)를 말한다.

 

실제 실행될때 스택 메모리에 어떻게 표현되는지 확인해보자

함수나 메서드가 호출될때마다 스택프레임이 저장된다.

args란?

C# 프로그램(exe. 실행파일)을 콘솔창에서 실행할 때, 실행파일 뒤에 프로그램에서 사용할 옵션(Command-Line Argument)들을 사용할 수 있다. 여기에 지정된 옵션들은 C# 프로그램의 args에 전달된다.

간단히 말해 프로그램을 실행할 때 넣어준 문자열들이 들어간다.

위의 이미지와 같이 exe뒤에 있는 문자열들이 args의 값으로 들어가게 된다. 명령어로 실행할 때만 값을 넣어줄 수 있기 때문에 일반적으로 exe파일을 더블 클릭하는 경우에는 args값에 값을 넣어줄 수 없다.

보통 프로그램의 다양한 옵션이나 설정을 전달하는데 사용된다고 한다.

더 복잡한 예로 한번 보자

class codepiggy
{
    public static void Main(string[] args)
    {
        int a = 100;
        a = pow(a);
    }
    
    public static int pow(int num)
    {
        int b = num*num;
        return b;
}

 

 


Heap 메모리

class codepiggy
{
    public static void Main(string[] args)
    {
        Counter c = new Counter();
    }
}
class Counter
{
    private int state = 0;
    public void increment() { state++; }
    public int get() { return state; }
}

코드상의 변수들을 알아보자

args : 매개 변수

c : 지역 변수

 

state는 지역변수일까?

Counter클래스를 만들게 되면서 인스턴스 상태를 저장하는 인스턴스 변수이다.

 

인스턴스 변수(instance variable)란?

인스턴스가 생성될때 생성되는 변수이다. 객체가 생성될 때마다 매번 새로운 변수가 생성되고 클래스 변수와 달리 공유되지 않는다.

 

1) stack에 main 메서드 stack frame과 Counter 메서 stack frame이 생성된다.

2) new Counter를 통해 객체가 생성되면 이는 Heap메모리에 할당된다. 그리고 생성자 처리가 끝나게 되면 counter stack frame은 삭제가 c라는 Counter 객체를 다를 수 있는변수를 만들고 여기에서 c에 해당되는 값은 counter객체를 가리키는 Heap메모리상의 객체의 주소 값이 저장된다.

 

복잡한 예제로 더 알아보자

class codepiggy
{
    public static void Main(string[] args)
    {
        Counter c = new Counter();
        two(c);
        int count = c.get();
    }
    public static void two(Counter c)
    {
        c.increment();
        c.increment();
    }
}
class Counter
{
    private int state = 0;
    public void increment() { state++; }
    public int get() { return state; }
}

 

1) 메인 메서드가 실행이 되면 main stack frame이 생김

2) Counter 클래스 객체 stack frame 생김

3) heap영역에 counter 객체가 생김

this 변수

생성자가 생김에 따라 생기는 this변수는 heap메모리 상에있는 객체를 가리키는 역할을 가진다. this안에 객체의 메모리 주소를 담고 있다.

5) two메서드 호출에 따라 two stack frame이 생긴다. 여기에서 파라미터로 c값을 전달했기 때문에 c 객체의 주소값이 들어간다.

6) 객체 c에 대한 increment메서드가 호출된다. 그러므로 increment stack frame이 생성된다. 인스턴스에 대한 메서드 호출이므로 암묵적으로 this라는 변수가 있다. 그러므로 여기에서 increment를 수행한다는 말은 state값이 1이 된다는 말이다.

state값이 1이되고 메서드가 종료되었으므로 increment와 관련된 stack frame은 없어진다. 

7) 6번과 같은 진행이 이루어진다. 결과적으로 state은 2가 된다.

8) two 메서드가 종료되었으므로 stack frame에서 제거된다.

9) get 메서드가 새로 시작되었으므로 새로운 get stack frame이 생성된다. 인스턴스에 대한 메서드 호출이므로 암묵적으로 this라는 변수가 있다. 그러므로 2라는 값이 반환이 된다. 반환이 되면서 get 메서드의 stack frame은 제거되고 stack 메모리에 count라는 지역변수의 이름으로 저장된다.


쓰레기 객체(Garbage Object)

class codepiggy
{
    public static void Main(string[] args)
    {
        Counter c = make();
    }
    public static void make(Counter c)
    {
        Counter c = new Counter();
        return new Counter();
    }
}
class Counter
{
    private int state = 0;
    public void increment() { state++; }
    public int get() { return state; }
}

 

make메서드를 실행하고 나면 두 개의 객체가 생성된다. 여기에서 c가 가리키는 객체의 주소 값은 return new Counter() 값에 해당한다. 그러므로 Counter c = new Counter()객체에 해당하는 값을 래퍼런스하고 있는게 없다. 그러므로 이 객체에 접근할 수 있는 방법이 없다.

그러므로 이렇게 접근할 수 없는 객체들이 Heap에 쌓이게 되면 어느 순간 Heap메모리가 고갈이 된다. 

Q : Heap 메모리가 고갈되서 메모리 부족 상태가 되면 어떻게 돼요?
A :  .NET 런타임은 GC를 통해 메모리를 회수하려 시도할 것이다. 하지만 확보되지 못하면 'OutOfMemoryException'예외를 발생시킨다.

그러므로 이러한 객체들을 주기적으로 제거해주어야 한다. 대표적으로 2가지 방법이 있다.

01 GC사용

.NET 런타임은 자동으로 가비지 컬렉션을 수행하여 더 이상 사용되지 않는 객체를 정리한다. 하지만 이또한 빈번하게 발생하면 성능 저하가 발생할 수 있다. 특히 대규모 객체 그래프를 정리해야 할 경우 GC의 실행 시간이 길어지게된다. 그러므로 우리는 따로 프로그래밍 명령어를 통해 관리를 해주는 것이 좋다.

 

02 프로그래밍 명령어사용

C#에서는 대표적으로 Dispose패턴을 사용한다. 이를 통해 리소스를 명시적으로 해제할 수 있다.

 

 

반응형

댓글