new 는 힙에 자리를 만들고 주소를 돌려준다
Dog a = new Dog();
이 한 줄에서 무슨 일이 일어나는지 봅니다. 여기가 앞으로 나올 이야기의 대부분을 설명합니다.
앞에서 변수는 값을 담는 자리라고 했습니다. int 하나면 4바이트 자리가 생기고 거기에 숫자가 들어갔습니다.
그럼 Dog 는 어떨까요. Dog 는 몇 바이트일까요.
Name 은 문자열이라 길이가 정해져 있지 않습니다.
상속을 하면 필드가 더 붙기도 합니다. 미리 정하기 어렵습니다.
그래서 객체는 다른 곳에 만들고, 변수에는 그 위치만 담습니다.
메모리는 두 군데로 나뉘어 있다
프로그램이 쓰는 메모리는 크게 두 덩어리입니다.
| 스택 | 힙 | |
|---|---|---|
| 무엇이 놓이나 | 지역 변수, 매개변수 | new 로 만든 객체 |
| 크기 | 작다 | 크다 |
| 언제 사라지나 | 그 블록을 벗어나면 바로 | 아무도 안 가리키게 되면 나중에 |
| 누가 치우나 | 자동 | 가비지 컬렉터(GC) |
| 속도 | 빠르다 | 상대적으로 느리다 |
스택은 앞에서 이미 만났습니다. 메서드를 부르면 자리가 쌓이고 끝나면 사라지던 그것입니다. 지역 변수도 거기 놓입니다. 그래서 메서드가 끝나면 지역 변수가 같이 사라집니다. 치우는 규칙이 단순해서 빠릅니다. 대신 자리가 작습니다. 재귀가 깊어지면 넘치던 이유입니다.
힙은 크기와 수명을 미리 못 정하는 것들을 놓는 곳입니다.
new 로 만든 객체가 여기 갑니다.
new 가 하는 일
Dog a = new Dog();
세 가지가 순서대로 일어납니다.
- 힙에
Dog하나가 들어갈 자리를 잡고 기본값으로 채웁니다. - 그 자리의 주소를 돌려줍니다.
- 그 주소를 스택의 변수
a에 담습니다.
변수 a 안에 개가 들어 있는 것이 아닙니다. 개가 있는 곳의 주소가 들어 있습니다.
이렇게 주소를 담는 타입을 참조 타입 이라고 합니다.
int 처럼 값을 직접 담는 타입은 값 타입 입니다.
클래스는 전부 참조 타입입니다. 배열도, string 도, List 도 그렇습니다.
앞에서 List 를 메서드에 넘겼을 때 밖이 같이 바뀌던 이유가 이것입니다.
복사된 것이 목록이 아니라 주소였습니다.
기본값은 null 이다
new 로 잡은 자리는 기본값으로 채워집니다. 배열을 만들 때와 같습니다.
숫자는 0, bool 은 false 입니다.
참조 타입 필드는 null 입니다. null 은 “아무것도 안 가리킴” 이라는 뜻입니다.
값이 0인 것과 다릅니다. 가리키는 대상이 없는 상태입니다.
Dog a = new Dog();
Console.WriteLine(a.Name.Length); // NullReferenceException
a.Name 이 null 인데 그 안의 Length 를 물었습니다.
아무것도 없는 주소로 찾아간 것이라 그 자리에서 죽습니다.
Dog a = null; // 변수 자체가 아무것도 안 가리킬 수도 있습니다
a.Bark(); // NullReferenceException
이 예외는 C# 을 쓰는 내내 만납니다. 원인은 언제나 같습니다. 아무도 안 가리키는 것을 따라간 것입니다.
아무도 안 가리키면 치워진다
Dog a = new Dog();
a.Name = "바둑";
a = new Dog(); // 다른 것을 가리키게 했습니다
처음 만든 개는 어떻게 될까요. a 가 다른 곳을 가리키게 됐으니
아무도 그 상자를 가리키지 않습니다. 주소를 잃어버렸으니 다시 찾아갈 방법도 없습니다.
C# 에서는 이런 상자를 직접 치우지 않습니다. 가비지 컬렉터가 가끔 돌면서 아무도 안 가리키는 것을 찾아 치웁니다.
편하지만 공짜는 아닙니다. GC 가 도는 동안 프로그램이 잠깐 멈춥니다. 평소에는 신경 쓸 일이 없는데, 게임처럼 매 프레임이 중요한 곳에서는 이 멈춤이 화면 끊김으로 보입니다.
그래서 반복문 안에서 new 를 계속 부르면 곤란합니다.
쓰레기를 계속 만들어 내는 것이니까요.
같은 객체를 다시 쓰는 방식이 그래서 나옵니다.
정리
- 메모리는 스택과 힙으로 나뉩니다. 지역 변수는 스택,
new로 만든 객체는 힙에 놓입니다. - 스택은 블록을 벗어나면 바로 치워집니다. 빠르지만 작습니다.
- 힙은 크기와 수명을 미리 못 정하는 것들이 놓입니다. GC 가 치웁니다.
new는 힙에 자리를 잡고 그 주소를 돌려줍니다. 변수에는 주소가 담깁니다.- 그래서 클래스는 참조 타입입니다.
int같은 값 타입은 값을 직접 담습니다. - 참조 타입의 기본값은
null입니다. 그걸 따라가면NullReferenceException입니다. - 아무도 안 가리키는 객체는 GC 가 치웁니다. 도는 동안 잠깐 멈춥니다.