1/ 참조 카운팅
포인터의 근본적인 문제는 "누가 이 객체의 주소를 들고 있는지 아무도 모른다"는 점이다. 그래서 객체를 가리키는 포인터들을 일일이 추적하는 대신, "지금 나를 참조하는 객체가 몇 개인가" 하는 그 횟수만 센다.
내가 이 객체를 들고 있겠다 —> AddRef (카운트 +1)
더 이상 쓰지 않는다 —> Release (카운트 -1)
이때 카운트 값이 0이 되면 delete한다.
1/ C의 malloc과 free는 메모리 덩어리만 주고받는다. 반면 C++의 new와 delete에는 생성자와 소멸자 호출이 얹힌다.
new = "영역 확보 + 생성자 호출"
delete = "소멸자 호출 + 영역 반납"인 셈이다.
소멸자는 객체가 떠안고 있던 책임 — 다른 객체 포인터, 파일 핸들 같은 것 — 을 정리한다. 메모리를 제대로 정리하지 않으면 다음과 같은 문제가 발생할 수 있다.
- Memory leak
- Double free
- Use after free
2-2/ 멀티스레드 환경에서의 문제
새 스레드가 raw 포인터 p를 받기만 하고 아직 AddRef를 호출하지 않은 상태에서, 메인 스레드가 먼저 Release로 카운트를 0으로 떨어뜨려 객체를 delete해 버리면, 뒤늦게 새 스레드가 AddRef하려는 대상은 이미 소멸된 객체가 된다.
결국 이런 문제를 해결하기 위해 스마트 포인터를 사용한다.
1/ 참조 카운팅
포인터의 근본적인 문제는 "누가 이 객체의 주소를 들고 있는지 아무도 모른다"는 점이다. 그래서 객체를 가리키는 포인터들을 일일이 추적하는 대신, "지금 나를 참조하는 객체가 몇 개인가" 하는 그 횟수만 센다.
내가 이 객체를 들고 있겠다 —> AddRef (카운트 +1)
더 이상 쓰지 않는다 —> Release (카운트 -1)
이때 카운트 값이 0이 되면 delete한다.
2-1/ 수동 참조 카운팅의 문제
참조 카운팅을 수동으로 처리하면 사람의 실수가 발생할 수 있다. AddRef와 Release는 항상 짝이 맞아야 정상적으로 동작하기 때문이다.
AddRef를 빼먹으면 카운트가 실제보다 작아져, 아직 사용 중인데도 0이 되어 객체가 삭제되는 use-after-free가 발생한다. 반대로 Release를 빼먹으면 카운트가 영영 0이 되지 않아 memory leak이 발생한다.
4-3/
Monster와 Player는 물리적으로 똑같은 바이트를 차지하는데, m은 그 칸을 hp라고 믿고 100을 써넣는다. 하지만 그 자리의 진짜 주인인 Player는 같은 칸을 target으로 읽는다.
나중에 player->target->...—이미 100으로 덮어써진 그 자리—을 따라가는 순간, 0x64를 역참조하면서 크래시가 터진다. 즉 player->target은 원래 주소값을 담아야 하는데 100으로 덮어써졌기 때문에, 주소 100을 가리키게 되면서 크래시가 발생한다.
정리하면 (1) 잘못된 쓰기가 조용히 성공하고, (2) 그 쓰기가 남의 객체 안에 시한폭탄을 심으며, (3) 그 폭탄은 인과관계가 끊긴 엉뚱한 곳에서 뒤늦게 터진다.
1/ C의 malloc과 free는 메모리 덩어리만 주고받는다. 반면 C++의 new와 delete에는 생성자와 소멸자 호출이 얹힌다.
new = "영역 확보 + 생성자 호출"
delete = "소멸자 호출 + 영역 반납"인 셈이다.
소멸자는 객체가 떠안고 있던 책임 — 다른 객체 포인터, 파일 핸들 같은 것 — 을 정리한다. 메모리를 제대로 정리하지 않으면 다음과 같은 문제가 발생할 수 있다.
- Memory leak
- Double free
- Use after free