C# 메모리 관리 – 값 타입과 스택(Value Types and Stacks)

C# 메모리 관리 – 값 타입과 스택(Value Types and Stacks)

 

memory management

값 타입과 스택(Value Types and Stacks)

Auto는 자동 변수를 의미합니다. C#에서는 더이상 사용되지 않기 때문에 이 키워드를 보지 못했을 수도 있습니다.
예전에, auto는 기본 변수를 의미했습니다:

int variable = 10;
auto int variable = 10;

위의 두 줄은 같은 내용입니다.

자동 변수는 콜 스택에 할당되는 특징을 가지고 있습니다.
스택(stack)은 접시가 쌓여 있는 것 같이, 변수가 차곡차곡 쌓여있는 형태이며 가장 위에 있는 것만 제거 및 추가하는 것이 가능합니다. 반대로 가장 아래에 있는 것은 제거하거나 추가할 수 없습니다.

stack

스택은 LIFO(Last In First Out)라고 불리며, 간단히 말해서 마지막에 넣은 값이 가장 먼저 출력으로 나오는 구조를 갖습니다.
프로그램은 스택의 가장 위에 있는 요소를 추적하기 위해서 스택 포인터를 사용합니다. 스택 포인터는 CPU내의 변수로서 스택의 현재 위치를 추적하는 역할을 하며 위의 그림에서 검정색 화살표로 표시되어 있습니다. 변수를 추가하면 스택 포인터가 증가합니다 (반대로 운영체제가 메모리 관리하는 방법에 따라서 스택 포인터가 줄어들 수도 있습니다)

스택은 함수 호출(콜)에 사용됩니다. 전체 프로그램이 main 함수에 있고, main 함수에서 Update 함수를 호출하며 이 Update 함수는 다른 함수들을 순차적으로 호출하는 프로그램을 생각해 볼 수 있습니다. 이 경우에 변수가 다른 방식으로 정의되지 않았다면, 스크립트의 모든 변수는 스택에 위치하는 자동 변수라고 볼 수 있습니다.

스택에 대해서 좀 더 자세히 살펴보려면, 스택 프레임(stack frame)이라는 개념에 대해서도 알아야 합니다. 간단히 말해서 현재 실행 중인 함수에 의해서 지역적으로 할당된 변수들의 집합이라고 보시면 됩니다 (함수가 return한 직 후에 실행될 지점을 가리키는 포인터인 함수 반환 주소 역시 스택 프레임에 포함됩니다). 컴파일러는 스택 프레임 끝을 가리키는 포인터를 관리하고 이 포인터로 부터 negative offset값을 사용해서 지역 변수가 할당된 메모리를 찾습니다. 이 정보를 통해서 함수가 리턴한 후에 지역 변수들에 의해서 할당되었던 메모리 공간이 회수됩니다 – 다음 함수 콜이나 다른 용도를 위해서 공간을 비워둡니다.

재귀(recursive) 프로그램(자기 자신을 호출하는 함수)을 만드는 경우, 각 함수 호출 마다 지역 변수와 함수 반환 주소를 스택에 저장하기 위한 공간이 필요하기 때문에, 잠재적으로 스택 공간 부족 문제가 발생할 수 있습니다 – 재귀 함수가 정지하지 않고 계속 호출되는 경우 버그를 발생시켜, stack overflow 오류 메시지가 발생할 수도 있습니다.

따라서 자동 변수의 스코프(scope)와 주기(lifetime)는 꼬리에 꼬리를 물고 이어지게 됩니다. 변수는 변수가 선언된 지점부터 중괄호가 닫히는 지점까지 유지됩니다. 다음의 예를 보겠습니다.

private void Update(){
    int number = 10;
    Fct(number);
    Debug.Log(number);
} 

private void Fct(int n){
    int inside=20;
    n = n + inside;
    Debug.Log(n);
}

stack2

Update 함수가 호출되고 첫번째 명령이 변수 선언입니다. 스택 포인터가 하나 증가하고 number의 값이 해당 메모리 위치에 저장됩니다.
두번째 명령은 함수 호출입니다. 함수를 호출하면, 프로그램이 해당 지점에서 멈추고 호출된 함수의 주소로 이동하면서 함수 호출 이전에 실행 지점을 저장하기 위한 정보가 스택에 전달됩니다.
함수에 필요한 파라미터(인자)들 역시 스택에 전달되고 원래 값이 복사되어 스택에 저장됩니다.

함수에서 사용 될 number변수는 스택에 저장되지 않고 새로운 변수 n이 생성되어 number변수의 저장된 값과 같은 값이 n변수에 저장되며, 이 n변수가 스택에 위치합니다.
이제 number변수의 값을 가진 변수 n이 함수 내에 생겼지만 number변수는 Fut()함수에 존재하지 않기 때문에 number변수에 접근할 수 없습니다.

Fct() 함수에는 int타입의 새 변수가 선언되었습니다. 스택은 아래 그림과 같은 모양을 하고 있습니다.
함수 끝 부분에서 n이 출력되고 값 30을 갖게됩니다. 함수내에서 생성된 모든 변수들은 제거되거나 정보를 잃어버립니다. 여기에는 변수 n도 포함됩니다. 여전히 변수가 메모리에 위치해 있다고 하더라도, 시스템에서 무시하거나 변수로 고려하지 않습니다.

다시 Update 함수에서 값 10을 가진 변수 number를 출력합니다. 왜냐하면, number변수가 Fct() 함수에서 사용된 것이 아니라 number의 값이 복사되었기 때문입니다. 스택 포인터는 변수 n의 위치와 저장된 값을 버립니다. 이때 Update 함수에서 다시 변수 n에 접근하려고하면, 컴파일러는 변수가 현재 스코프에 존재하지 않는다는 내용의 오류를 발생시킵니다.

바로 전에 자동 변수는 그 변수가 선언된 중괄호 { } 에 의해서 스코프가 정의된다고 설명했습니다. 이 영역 밖에서는, 해당 변수가 존재하지 않으며 변수에 접근할 수 없습니다. 중괄호를 사용해서 함수, if 구문 및 루프의 스코프를 정의합니다.

Lifetime 및 스코프

Lifetime은 프로그램 안에서 변수가 존재하는 시간을 의미하고, 스코프는 변수가 존재 할 프로그램의 지역(zone)을 정의합니다.
값 타입의 모든 변수들은 스택에 저장됩니다. 아래 나열된 타입들이 값 타입입니다.
* int
* unsigned
* float
* double
* char
* struct
* bool
* byte
* enum
* long
* short

C에 존재하지 않았던 불리언(bool)을 제외한 모든 타입들이 C 언어로부터 계승되었습니다.
자동 변수의 주기 lifetime은 컴파일러가 관리합니다. 변수가 선언되었던 스코프가 끝나면 변수의 주기 lifetime도 끝납니다. 이 과정은 자동으로 관리되기 때문에 프로그래머가 나중에 메모리를 사용하기 위해서 메모리를 해제할 필요가 없습니다.

RonnieJ

프리랜서 IT강사로 활동하고 있습니다. 게임 개발, C++/C#, 1인 기업에 관심이 많습니다.

답글 남기기

이메일 주소는 공개되지 않습니다.

Please turn AdBlock off

Notice for AdBlock users

Please turn AdBlock off