C++에서 구조체나 클래스를 사용할 때 어느 메모리 영역에 할당될까? 아래 예제는 간단한 구조체를 정의하고 메모리 영역에 생성하는 예제이다.
struct Point {
int x = 1;
int y = 1;
};
int main() {
// 1)
Point pt1;
Point pt2 = pt1;
// 2)
Point* p1 = new Point;
Point* p2 = p1;
delete p1;
}
Point 구조체를 생성하는 부분 중 1)에 해당하는 부분을 그림으로 나타내난 아래 그림과 같다. pt1이 생성되고 pt2가 생성되면 pt1값이 pt2로 복사된다. pt1.x을 바꾸어도 pt2에 영향을 미치지 않는다.

new를 이용해 Point를 생성하는 2)에 해당하는 부분을 그리면 아래와 같이 그릴 수 있다. p1에는 Heap에 생성된 Point 구조체의 주소가 들어가게 되고 p2에는 p1의 값이 복사된다. p1과 p2는 결국 같은 Heap 주소를 가리키고 서로의 변경사항이 영향을 미치게 된다.

위의 그림을 이해하고 아래의 출력값을 확인해보면 스택 주소끼리는 비슷하지만 힙 주소와는 주소값의 차이가 심한것을 알 수 있다.
int main() {
Point pt1;
Point pt2 = pt1;
Point* p1 = new Point;
Point* p2 = p1;
cout << &pt1 << endl; // 스택 주소
cout << &pt2 << endl;
cout << p1 << endl; // 힙 주소
cout << p2 << endl;
cout << &p1 << endl; // 스택 주소
cout << &p2 << endl;
delete p1;
}

C와 C++은 생성될 객체의 메모리 위치를 사용하는 사람이 결정한다. new를 이용해 생성하면 힙에 그렇지 않으면 스택에 할당이 될 것이다.
C#에서 구조체와 클래스를 사용할 때는 C++과 다르다. 객체가 생성될 위치를 정의하는 사람이 정할 수 있고 구조체와 클래스는 아래와 같은 특징을 가진다.
아래 코드는 위에서 사용한 Point를 구조체 방식과 클래스 방식으로 나타낸 코드이다.
struct SPoint
{
public int x = 0;
public int y = 0;
public SPoint(int xPos, int yPos)
{
x = xPos; y = yPos;
}
}
class CPoint
{
public int x = 0;
public int y = 0;
public CPoint(int xPos, int yPos)
{
x = xPos; y = yPos;
}
}
class Program
{
public static void Main(string[] args)
{
SPoint sp1 = new SPoint(1, 1);
CPoint cp1 = new CPoint(1, 2);
SPoint sp2 = sp1;
CPoint cp2 = cp1;
sp2.x = 10;
Console.WriteLine(sp1.x);
cp2.x = 10;
Console.WriteLine(cp1.x);
}
}
struct는 스택에, class는 힙에 객체가 생성된다고 하였으니 위 코드의 결과를 쉽게 예측할 수 있다. sp1은 new를 사용했지만 struct이므로 스택에 할당되고 sp2에는 sp1의 값이 복사될 것이다. cp1는 new를 사용했고 class이므로 힙영역에 객체가 생성되고 cp1에는 객체의 메모리 주소가 들어간다.
cp2에는 cp1의 값이 들어가므로, sp2는 sp1와 서로 다른 객체(변경의 영향을 받지 않음)이고 cp2는 cp1과 같은 객체를 가리킨다(변경의 영향을 받음). 출력의 결과는 1, 10이 될 것이다.
C#에는 미리 정의된 자료형이 존재하는데 자료형에따라 value type인지 reference type인지 다르다.
미리 구분하자면 struct(int, double 등)와 enum과 같은 수치 관련 타입은 value type이다. class(object, string, array 등), interface, delegate와 같은 닷넷 프레임워크에 존재하는 클래스 라이브러리들은 reference type이다.
정수형인 int는 내부적으로 Int32라는 struct로 구현되어 있다. 즉 value type이다. 아래 코드를 실행해도 기존 초기화 값인 1이 출력된다.
public static void Main(string[] args)
{
// 1. int
int n1 = 1;
int n2 = n1;
n2 = 10;
Console.WriteLine(n1); // 1
}
모든 배열은 System.Array라는 것으로부터 파생되는데 System.Array는 내부적으로 class로 구현되어 있고 즉 reference type이다. arr2[0]를 10으로 변경하면 arr1[0]의 값이 10으로 변한다.
public static void Main(string[] args)
{
// 2. array
int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1;
arr2[0] = 10;
Console.WriteLine(arr1[0]); // 10
}
string의 정의는 class로 되어있다. 그렇다는 것은 reference type라는 것이고 s2를 'World'로 변경하면 s1가 영향을 받을 것으로 예상된다.
하지만, 그렇지 않은데 s2 = "World"가 의미하는 것이 s2 = new string("World")이기 때문이다. 즉, 새로운 string 객체를 생성하는 것을 의미하고 기존의 'Hello'와 다른 위치에 'World'가 생성되는 것이다.
public static void Main(string[] args)
{
// 3. string
string s1 = "Hello";
string s2 = s1;
s2 = "World"; // s2 = new string("World")
Console.WriteLine(s1);
}
string은 상태를 변경할 수 없는 immutable한 객체이기 때문이라고 한다.