대입하면 값이 복사되거나 주소가 복사된다
Esc

    대입하면 값이 복사되거나 주소가 복사된다

    –C#기초메모리

    두 코드를 비교해 봅시다.

    int x = 10;
    int y = x;
    y = 20;
    
    Console.WriteLine(x);      // 10
    Dog a = new Dog("바둑");
    Dog b = a;
    b.Name = "초코";
    
    Console.WriteLine(a.Name); // 초코

    같은 모양인데 결과가 반대입니다. x 는 안 바뀌고 a.Name 은 바뀌었습니다.

    규칙이 다른 것이 아닙니다. 규칙은 하나입니다. 담긴 것이 복사됩니다. 담긴 것이 무엇이냐가 다를 뿐입니다.

    담긴 것이 값이면 값이 복사된다

    int 는 값 타입입니다. 변수 안에 숫자가 그대로 들어 있습니다. y = x 는 그 숫자를 옮겨 적습니다. 자리가 둘이 됩니다.

    담긴 것이 주소면 주소가 복사된다

    Dog 는 참조 타입입니다. 변수 안에 든 것은 개가 아니라 개가 있는 곳의 주소입니다. b = a 는 그 주소를 옮겨 적습니다.

    주소를 복사했으니 둘이 같은 곳을 가리킵니다. 개는 여전히 한 마리입니다.

    마지막 단계가 헷갈리는 자리입니다. 두 가지를 구분해야 합니다.

    하는 일 무엇이 바뀌나 다른 변수에 보이나
    b.Name = "초코" 가리키는 대상의 내용 보인다
    b = new Dog(...) b 가 가리키는 곳 안 보인다

    앞에서 메서드에 List 를 넘기면 밖이 바뀐다고만 하고 넘어간 자리가 있습니다. 이유가 이것입니다. 복사된 것이 목록이 아니라 주소였습니다. list.Add(99) 는 가리키는 대상을 고친 것이라 밖에 보이고, list = new List<int>() 는 가리키는 곳을 바꾼 것이라 안 보입니다.

    어떤 것이 값이고 어떤 것이 참조인가

    값 타입 참조 타입
    무엇이 있나 int, double, bool, char, enum, struct class, 배열, string, List 같은 것들
    변수에 담긴 것 값 자체 주소
    어디에 놓이나 보통 스택 힙 (변수는 스택)
    대입하면 값이 복사된다 주소가 복사된다
    null 이 될 수 있나 안 된다 된다

    string 이 참조 타입이라는 것이 걸릴 수 있습니다. 앞에서 문자열은 바꿀 수 없다고 했는데, 그래서 값처럼 행동합니다.

    string s1 = "안녕";
    string s2 = s1;
    s2 = "잘가";           // s2 가 다른 문자열을 가리키게 된 것
    
    Console.WriteLine(s1); // 안녕

    주소가 복사되긴 했는데, 가리키는 대상을 고칠 방법이 없으니 문제가 안 생깁니다. 문자열을 불변으로 만든 이유 중 하나가 이것입니다.

    struct 는 값 타입이다

    클래스와 거의 같게 생겼는데 값 타입인 것이 있습니다. struct 입니다.

    struct Point
    {
        public int X;
        public int Y;
    }
    
    Point p1 = new Point { X = 1, Y = 2 };
    Point p2 = p1;
    p2.X = 99;
    
    Console.WriteLine(p1.X);    // 1, 안 바뀝니다

    class 를 struct 로 바꾸기만 했는데 결과가 반대가 됐습니다. struct 는 변수 안에 내용이 통째로 들어 있어서, 대입하면 내용 전체가 복사됩니다.

    class struct
    타입 참조 값
    대입하면 주소가 복사 내용이 통째로 복사
    null 된다 안 된다
    상속 된다 안 된다

    언제 struct 를 쓰나. 작고, 값처럼 다루는 것이 자연스러운 묶음일 때입니다. 좌표, 색상, 크기 같은 것들입니다. (3, 4) 라는 좌표는 그 자체가 값이지 “어떤 좌표 객체” 가 아닙니다.

    크면 쓰면 안 됩니다. 대입할 때마다 통째로 복사되니 필드가 많으면 그게 부담입니다. 애매하면 class 를 씁니다. 실무에서 struct 를 직접 만들 일은 드뭅니다.

    박싱은 값을 힙으로 옮겨 담는 것이다

    값 타입을 참조 타입 자리에 넣으면 문제가 생깁니다. 값은 스택에 있는데 참조 타입 자리는 주소를 기대하기 때문입니다.

    C# 은 이때 힙에 상자를 하나 만들고 값을 거기 넣습니다. 이걸 박싱이라고 합니다.

    int n = 42;
    object o = n;        // 박싱: 힙에 상자를 만들어 42 를 넣는다
    int m = (int)o;      // 언박싱: 다시 꺼낸다

    object 는 모든 타입의 부모입니다. 무엇이든 받을 수 있는 자리인데, 참조 타입이라 값을 그대로는 못 담습니다.

    문제는 비용입니다. 박싱은 new 를 부르는 것과 같습니다. 힙에 자리를 잡고, 나중에 GC 가 치웁니다.

    for (int i = 0; i < 100000; i++)
    {
        object o = i;    // 10만 번 박싱, 10만 개의 쓰레기
    }

    반복문 안에서 일어나면 눈에 띄게 느려집니다. 이 문제를 해결하는 것이 제네릭인데, 그건 뒤에서 다룹니다. List<int> 의 꺾쇠가 바로 그것입니다.

    정리

    • 대입할 때 복사되는 것은 변수에 담긴 것입니다. 규칙은 하나입니다.
    • 값 타입은 값이 담겨 있어서 값이 복사됩니다. 자리가 둘이 됩니다.
    • 참조 타입은 주소가 담겨 있어서 주소가 복사됩니다. 둘이 같은 것을 가리킵니다.
    • 가리키는 대상의 내용을 바꾸면 다른 변수에도 보입니다.
    • 변수가 가리키는 곳을 바꾸면 자기만 바뀝니다.
    • string 은 참조 타입이지만 고칠 수 없어서 값처럼 행동합니다.
    • struct 는 값 타입입니다. 작고 값처럼 다룰 묶음에만 씁니다. 애매하면 class 입니다.
    • 값 타입을 object 자리에 넣으면 박싱이 일어납니다. 힙에 상자가 생기는 일입니다.