위에서 RoboCat 클래스에 추가된 내용이 어떻게 메모리에 저장되는지를 살펴보면, 직렬화가 꽤 골치 아플 것을 알 수 있다. 표 4-2에 전송 전/후의 메모리 레이아웃을 나타내었다.
▼ 표 4-2 복잡해진 RoboCat의 메모리 레이아웃
주소 |
필드 |
원본값 |
대상 초깃값 |
대상 최종값 |
바이트 0-3 |
vTablePtr |
0x0A131400 |
0X0B325080 |
0x0A131400 |
바이트 4-7 |
mHealth |
0x00000005 |
0x0000000A |
0x00000005 |
바이트 8-11 |
mMeowCount |
0x00000002 |
0x00000003 |
0x00000002 |
바이트 12-15 |
mHomeBase |
0x0D124008 |
0x00000000 |
0x0D124008 |
바이트 16-143 |
mName |
Fuzzy\0 |
\0 |
Fuzzy\0 |
바이트 144-167 |
mMiceIndices |
?????? |
?????? |
?????? |
이제 RoboCat의 첫 4바이트는 가상 함수 테이블(virtual function table)을 가리키는 포인터가 되었다. 이것도 32비트 아키텍처일 때만 그렇고 64비트 아키텍처에서는 8바이트가 된다. RoboCat에 가상 함수 RoboCat::Update()가 추가되었으므로, 각 RoboCat 인스턴스는 자신의 가상 함수가 실제 구현된 지점을 가리키는 함수 포인터를 갖고 있어야 한다. 이 포인터 값을 나이브하게 송수신하면 제대로 동작하지 않는데 왜냐하면 실행되는 프로세스마다 가상 함수 테이블의 위치가 같다는 보장이 없기 때문이다. 위의 경우에 수신자의 멀쩡하던 가상 함수 테이블 포인터 값 0x0B325080이 데이터가 리플리케이션, 즉 복제 전달되는 과정에서 엉뚱한 값으로 덮어 쓰이고 만다. 그 결과 수신자가 갱신 받은 RoboCat 객체의 Update()를 호출할 때, 운이 좋으면 메모리 접근 예외가 발생하고 끝나지만, 대개는 엉뚱한 위치의 코드를 실행하게 되어 훨씬 심각한 현상이 야기될 수 있다.