개발을 하던 중 crash가 발생 해 프로그램이 죽는 현상이 지속되어 문제를 해결한 방법에 대해 포스팅을 하겠다.
복사 생성자와 딥 카피에 대해 다루도록 하겠다.
a라는 객체를 생성하여 글로벌 변수에서 가지고 있어야 하는 상황이었다.
글로벌 변수는 포인터로 정의가 되어있었고, a라는 객체는 내부적으로 포인터 필드를 여러 개 가지고 있는 상황이었다.
로직에서 글로벌 변수를 복사 생성자의 매개변수로 넘겨준 뒤 메모리 해제를 하였는데 프로그램이 죽는 현상이 있었다. 디버깅을 하며 문제를 찾았는데 복사 생성과정에서 특정 필드가 얕은 복사되었다. 이 후 메모리 해제 과정에서 이중 메모리 해제가 발생하여 프로그램이 정상 종료되지 않았던 것이다.
예시를 들어보겠다.
class Test {
public:
int* p_num = nullptr;
Test(int* p) {
p_num = p;
}
Test(const Test& other) {
this->p_num = other.p_num;
}
~Test() {
if (p_num != NULL)
delete p_num;
}
};
Test
클래스에는 복사 생성자가 존재한다. p_num
포인터에 매개변수로 받은 other
의 포인터 변수의 값을 넘겨준다.
int* a = new int(10);
Test* test1 = new Test(a);
Test* test2 = new Test(*test1);
std::cout << test1->p_num << std::endl;
std::cout << test2->p_num << std::endl;
test1
과 test2
는 동일한 값을 가리키는 걸 볼 수 있다.
여기서 test1
의 메모리를 해제하게 되면 test2
의 포인터 필드가 가리키는 값이 존재하지 않게 되는 문제가 발생하게 된다. 이것이 얕은 복사이다.
위 사진을 보면 test1->p_num
과 test2->p_num
이 가르키는 주소값이 같다는 걸 확인할 수 있다.
test1
을 메모리 해제하게 되면 0xddddddd~~
와 같은 주소로 변경이 된다.
그러나 test2->p_num
이 가리키는 주솟값은 여전히 동일하다. 그러나 내부 값은 -57662307
로 쓰레기로 변한다.
이것으로 보아 얕은 복사의 경우 단순히 주솟값을 넘기기 때문에 가리키는 메모리가 해제된 것을 알 수 없는 것 같다. 이 상태에서 test2
를 메모리 해제시키면 에러가 발생하게 되는 것이다.
딥카피를 하기 위해서는 아래와 같이 복사 생성자를 작성해야한다.
Test(const Test& other) {
this->p_num = new int(*other.p_num);
}
이렇게 하면 새로운 객체를 힙에 할당하기에 메모리 해제 시 서로에게 영향을 주지 않는다.
위 예제 코드들은 간단하기 때문에 쉽게 에러를 찾을 수 있는데
코드가 많은 경우에는 찾기가 정말 쉽지 않다.
개발 단계에서부터 신중하게 고려하며 작성하는 것이 중요할 것 같다.