7. 바이너리 직렬화의 엔디안/정렬/레이아웃 가정

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 바이너리 직렬화의 엔디안/정렬/레이아웃 가정
// ----------------------------------------------------------------------------
// 시나리오:
//   - 서버는 x86-64 리눅스(.NET), 클라는 Windows/x86-64 와 일부 모바일(ARM, Unity IL2CPP)이다.
//   - 스냅샷 패킷 S_EntityState 를 "구조체를 통째로 바이트로 복사"해서 보낸다(속도 위해).
//   - 와이어 포맷은 사실상 "구조체의 메모리 레이아웃(필드 순서/패딩)"이 그대로 정의가 된 상태다.
//   - 최근 모바일(ARM) 빌드에서 좌표가 깨지고, 가끔 패킷 크기가 안 맞는다는
//     리포트가 들어왔다.
//
// 요구사항:
//   - 모든 플랫폼/런타임에서 동일한 바이트 시퀀스로 직렬화되어야 한다.
//   - 와이어 포맷은 머신/런타임 독립적으로 명세 가능해야 한다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 플랫폼 간 불일치가 나는지 설명하고,
//   수정안과 더 나은 직렬화 설계를 제시하라.
// ============================================================================

using System;
using System.Runtime.InteropServices;

// (A) 와이어로 그대로 나가는 구조체. 명시적 레이아웃/팩 지정 없음.
[StructLayout(LayoutKind.Sequential)]
public struct S_EntityState
{
    public byte   type;       // 엔티티 종류
    public uint   entityId;
    public float  posX;       // 위치
    public float  posY;
    public ushort hp;
    public ulong  flags;      // 상태 비트
}

public class StateSerializer
{
    // (B) 구조체를 통째로 바이트 버퍼로 복사 (Marshal 사용)
    public byte[] Serialize(S_EntityState s)
    {
        int size = Marshal.SizeOf<S_EntityState>();
        byte[] outBuf = new byte[size];
        IntPtr p = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(s, p, false);
        Marshal.Copy(p, outBuf, 0, size);
        Marshal.FreeHGlobal(p);
        return outBuf;
    }

    // (C) 수신: 바이트 버퍼를 구조체로 되돌린다
    public S_EntityState Deserialize(byte[] buf, int len)
    {
        int size = Marshal.SizeOf<S_EntityState>();
        IntPtr p = Marshal.AllocHGlobal(size);
        Marshal.Copy(buf, 0, p, size);                    // len 무시
        S_EntityState s = Marshal.PtrToStructure<S_EntityState>(p);
        Marshal.FreeHGlobal(p);
        return s;
    }
}

// ---------------------------------------------------------------------------
// 멀티바이트 정수를 직접 쓰는 헬퍼도 한 군데 있는데, 여기도 가정이 있다.
// ---------------------------------------------------------------------------
public class IntWriter
{
    // (D) 32비트 정수를 버퍼에 쓴다 — 호스트 바이트 순서 그대로
    public void WriteU32(byte[] buf, int offset, uint v)
    {
        byte[] tmp = BitConverter.GetBytes(v);            // 호스트 엔디안
        Array.Copy(tmp, 0, buf, offset, 4);
    }

    public uint ReadU32(byte[] buf, int offset)
    {
        return BitConverter.ToUInt32(buf, offset);        // 호스트 엔디안
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.