기존 객체와 같은 값을 가진 객체를 복사할 때 값 형식이냐 참조 형식이냐에 따라 얕은 복사와 깊은 복사의 개념이 나눠진다.
stack 영역
- 프로그램이 자동으로 사용하는 임시 메모리 영역이다.
- 지역 변수, 매개변수, 리턴 값 등 잠시 사용되었다가 사라지는 데이터를 저장하는 영역이다.
- 함수 호출 시 생성되고, 함수가 끝나면 시스템에 반환 된다.
- 스택 사이즈는 각 프로세스마다 할당 되지만 프로세스가 메모리에 로드 될 때 스택 사이즈가 고정되어 있어, 런타임 시에 스택 사이즈를 바꿀 수는 없다.
- 명령 실행시 자동 증가/감소 하기 때문에 보통 메모리의 마지막 번지를 지정 한다.
heap 영역
- 동적으로 메모리를 할당 하고자 할 때 위치하는 메모리 영역으로 동적 데이터 영역이라고 부르며, 메모리 주소 값에 의해서만 참조되고 사용되는 영역이다.
- 이 영역에 데이터를 저장 하기 위해서 C는 malloc(), C++은 new() 함수를 사용한다.
new int(3)로 객체를 생성하면 heap 공간에 메모리를 할당하고 값 3을 저장한다. 포인터 변수 a로 100번지 주소를 가리키게 한다.
new int(5)로 객체를 생성하면 heap 공간에 메모리를 할당하고 값 5를 저장한다. 포인터 변수 b로 200번지 주소를 가리키게 한다.
a = b 를 함으로써 a 는 b의 주소를 가리키게 된다. 즉, b주소가 가진 값 5를 출력한다.
동적으로 할당한 메모리는 포인터가 진입점인데 100번지 메모리 공간은 진입점을 잃어버려 Garbage가 된다.
복사가 발생하여
대상체는 1개인데, 대상체에 접근할 수 있는 포인터가 2개인 경우를 shallow copy(얕은 복사)라고 한다.
대상체가 2개인데, 대상체에 접근할 수 있는 포인터가 각각 1개인 경우를 deep copy(깊은 복사)라고 한다.
Java, C#에서는 Garbage Collector 가 삭제를 해주는데 C++에서는 없다.
#include <iostream> using namespace std;
int main() {
int* a = new int(3); // 메모리 공간 할당 int* b = new int(5);
cout << "a의 주소(복사전) : " << a << endl; cout << "b의 주소(복사전) : " << b << endl;
a = b; // 얕은 복사(참조 복사) ← b의 주소를 a에 복사 즉 a 는 b의 주소 200을 가리킨다. // *a = *b; // 깊은 복사(값 복사)
*b = 10;
cout << "a의 주소(복사후) : " << a << endl; cout << "b의 주소(복사후) : " << b << endl;
cout << "a의 값 : " << *a << endl; cout << "b의 값 : " << *b << endl;
delete a; // a가 가리키고 있는 메모리 공간 해제 delete b; }
|
얕은 복사를 했을 경우 아래와 같은 에러가 발생한다.
주소값을 참조하기 때문에 복사를 하였더라도 같은 주소를 참조하게 된다.
소멸자가 호출될 때 같은 메모리를 참조하기 때문에 한번 delete 된 메모리를 다시 delete 하기 때문에 오류 창이 뜬다.
깊은 복사를 할 경우에는 에러가 발생하지 않는다.
깊은 복사는 주소가 가리키는 값 즉, 그 메모리의 값을 복사하는 형태다.
새로운 메모리를 할당하기 때문에 원본과 복사본의 참조 공간이 다르므로 소멸자도 오류가 발생하지 않는다.
얕은 복사와 깊은 복사를 좀 더 이해하기 위해서
구글에서 영문으로 shallow copy vs deep copy c++ example 검색하여
https://owlcation.com/stem/Copy-Constructor-shallow-copy-vs-deep-copy 사이트를 찾아 읽어보고
Visual Studio 2019 Community 에서 테스트하고 적어둔다.
#include <iostream> using namespace std;
class ShalloC { private: int* x; //Sample 01: Private Data Member // This class contains only one integer pointer as private data member. public: //Sample 02: Constructor with single parameter ShalloC(int m) { cout << "생성자 호출" << endl; x = new int; *x = m; // The constructor will create a memory location in a heap // and copy the passed in value m to the heap content. }
//Sample 08: Introduce Copy Constructor and perform Deep Copy ShalloC(const ShalloC& obj) { cout << "복사 생성자 호출" << endl; x = new int; *x = obj.GetX(); // The statement x = new int; will create the new heap location // and then copies the value of obj content to new heap location. }
//Sample 03: Get and Set Functions int GetX() const { return *x; } void SetX(int m) { cout << "SetX " << endl; *x = m; }
//Sample 04: Print Function void PrintX() { cout << "Int X = " << *x << endl; }
//Sample 05: DeAllocate the heap ~ShalloC() { cout << "소멸자 호출" << endl; delete x; // new 로 할당한 메모리 해제 } };
int main() { //Sample 06: Create Object 1 and copy that to Object 2. // Print the data member for both Object 1 & 2. ShalloC ob1(10); ob1.PrintX(); ShalloC ob2 = ob1; ob2.PrintX();
//Sample 07: Change the Data member value of Object 1 // And print both Object 1 and Object 2 cout << endl; ob1.SetX(12); // ob1 객체 값 변경 ob1.PrintX(); // ob1 객체의 값 출력 ob2.PrintX(); // ob2 객체의 값 출력 }
|
In the Program main we created two Objects ob1 and ob2.
The object ob2
is created using the copy constructor. How? And where is the "copy
constructor".?
If you look at the statement ShalloC ob2 = ob1 ;
you clearly know that the ob2 is not yet created and in the mean time
ob1 is already created.
Hence, a copy constructor is invoked. Even
though the copy constructor not implemented, the compiler will provide
default copy constructor. Once both the objects are created we print the
values in ob1 and ob2.
실행결과
ob1 객체의 값을 변경하고 나서 ob1 의 값과 ob2의 값이 서로 달라진 걸 확인할 수 있다.
이렇게 값을 변경해 봄으로써 깊은 복사(deep copy)가 된 것인지 얕은 복사(shallow copy)가 된 것인지 명확히 알 수 있다.
복사 생성자 코드 부분을 주석처리하고 테스트 해보면 이해가 명확해진다.
default 복사 생성자가 자동으로 추가되어 얕은 복사를 수행했고, 소멸자는 같은 메모리 주소를 2번 삭제하려고 하다보니 에러 메시지를 출력한다.