Deep Copy
같은 클래스로 서로 다른 객체를 생성할 때, 클래스 내부의 필드 값을 각각 다르게 설정하려면 보통 new 키워드로 객체를 생성한 후 초기화 작업을 반복해야 한다.
하지만 이 작업을 간소화하려면, 클래스 내부에 자기 자신의 타입을 반환하는 메서드를 만들어 새로운 객체를 생성할 수 있다.
class Knight
{
public int hp;
public int attack;
public Knight Clone()
{
Knight knight2 = new Knight();
knight2.hp = this.hp;
knight2.attack = this.attack;
return knight2;
}
}
위 코드는 같은 클래스에서 서로 다른 객체를 생성할 때, 깊은 복사(Deep Copy)를 구현하는 예시다.
깊은 복사는 객체의 필드 값까지 복사하여 완전히 독립적인 객체를 만드는 것을 말한다.
이를 통해 원본 객체와 복사된 객체가 서로 간섭하지 않도록 할 수 있다.
이렇게 객체를 복사하거나 사용할 때, 객체가 저장되는 메모리 구조를 이해하는 것이 중요하다.
객체가 어떻게 메모리에 저장되는지는 성능 최적화나 올바른 설계에 큰 영향을 미치기 때문이다.
이를 이해하기 위해 스택(Stack)과 힙(Heap) 메모리 구조를 알아보자.
메모리 구조 - 스택(Stack)과 힙(Heap)
- 스택(Stack):
스택은 임시 데이터를 저장하는 데 사용되며, 함수의 매개변수나 함수 내에서 선언된 지역 변수가 저장된다.
스택은 함수가 실행되는 동안 공간을 할당받고, 함수가 종료되면 자동으로 메모리가 해제된다.
이와 같은 특징 덕분에 스택은 접근 속도가 빠르고, 메모리 관리를 프로그래머가 직접 하지 않아도 되는 장점이 있다.
- 힙(Heap):
힙은 프로그램 실행 중 동적으로 메모리를 할당하는 공간이다.
예를 들어, new 키워드를 사용해 객체를 생성하면 힙에 메모리가 할당된다.
힙은 스택과 달리 자동으로 해제되지 않으며, 사용이 끝난 후 직접 메모리를 해제하거나, 언어에서 제공하는 가비지 컬렉션(Garbage Collection)에 의해 관리된다.
힙은 스택보다 접근 속도가 느리지만, 큰 데이터를 동적으로 저장할 수 있다.
스택과 힙의 차이를 이해하면 자연스럽게 값 타입과 참조 타입의 동작 원리를 이해할 수 있다.
값 타입과 참조 타입은 메모리 구조와 데이터 관리 방식에 따라 다르게 동작하기 때문이다.
값 타입(Value Type)과 참조 타입(Reference Type)
- 값 타입(Value Type):
값 타입은 구조체(struct)와 같은 복사 타입으로, 변수 자체가 데이터를 저장한다.
즉, 스택 영역에 값 그 자체가 저장되며, 복사 시 독립적인 메모리를 차지한다.
따라서, 값을 복사해도 원본 변수와 복사본 변수는 서로 영향을 미치지 않는다.
- 참조 타입(Reference Type):
참조 타입은 클래스(class)와 같이, 데이터의 실제 내용이 힙에 저장되고, 변수는 힙 데이터를 참조하는 주소를 스택에 저장한다. 따라서, 같은 객체를 여러 변수가 참조할 수 있다.
이러한 경우, 한 변수에서 객체의 데이터를 변경하면, 이를 참조하는 다른 변수도 영향을 받게 된다.
값 타입과 참조 타입의 차이는 객체를 함수로 전달할 때 더욱 두드러지게 나타난다.
특히, 함수의 매개변수를 통해 값 타입을 참조로 전달하면 원본 데이터가 수정될 수 있다.
이를 위해 C#에서는 ref 키워드를 제공한다
참조 전달(Reference Passing)
C#에서는 함수 매개변수에 ref 키워드를 사용하여, 참조 타입뿐만 아니라 값 타입도 스택의 주소를 참조하도록 설정할 수 있다.
이를 통해 함수 내부에서 매개변수를 수정하면, 호출한 쪽에서도 수정된 값이 반영된다.
예를 들어, 값 타입 변수를 함수에 전달할 때 ref를 사용하지 않으면 복사본만 전달되므로 원본 값은 수정되지 않는다.
그러나 ref를 사용하면 원본 변수의 주소를 참조하므로, 함수 내 수정이 원본 변수에 영향을 미친다.
이처럼 참조 전달은 메모리 사용을 최적화하고 데이터 관리 방식을 유연하게 해준다.
이제 이러한 개념을 바탕으로, 스택과 힙 메모리 구조의 주요 차이를 요약해보자.
- 스택(Stack)
- 컴파일 시 미리 메모리가 할당된다.
- 함수가 실행되면 생성되고, 함수 종료 시 자동으로 해제됨
- 접근 속도가 빠르다.
- 주로 지역 변수와 매개변수를 저장
- 값 타입 변수가 주로 사용된다.
- 힙(Heap)
- 실행 중 동적으로 메모리를 할당한다.
- 사용 후 명시적으로 해제하거나, 가비지 컬렉션으로 관리된다.
- 접근 속도가 스택보다 느리다.
- 주로 new로 생성된 객체를 저장한다.
- 참조 타입 변수가 힙 데이터를 가리킨다.