18. GCHandle/fixed 핀닝과 GC 압축(compaction)의 상호작용

난이도 상 해설 보기 →

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

결함 코드 · C#
// 주제: GC 와 포인터의 상호작용 — fixed 문과 GCHandle 핀닝(pinning), 그리고 힙 압축.
//
// .NET GC 는 수집 시 살아있는 객체를 이동(compaction)시켜 단편화를 줄인다. 관리 객체의
// 메모리를 네이티브 포인터로 다루려면(소켓 I/O 버퍼, P/Invoke) 그동안 GC 가 그 객체를
// "움직이지 못하게" 고정해야 한다. 짧게는 unsafe `fixed` 문이, 오래는 GCHandle(Pinned)이
// 그 역할을 한다. 핀 고정은 이동을 막아 포인터를 안정시키지만, 힙 압축을 방해해 단편화를
// 유발한다.
//
// 과제:
//   1) 이 프로그램의 정확한 표준출력을 예측하라(네 줄).
//   2) fixed 블록 안에서 얻은 포인터가 왜 그 블록 동안만 유효한지, 블록을 벗어나면
//      무엇이 달라지는지 설명하라.
//   3) GCHandle(Pinned) 이 GC.Collect() 뒤에도 AddrOfPinnedObject() 를 불변으로
//      유지하는 이유를 설명하라.
//   4) 포인터로 쓴 값이 왜 managed 배열 뷰(buf[..])에도 그대로 보이는지(같은 저장소),
//      그리고 오래 핀 고정하는 것이 왜 단편화를 유발하는지 설명하라.

using System;
using System.Runtime.InteropServices;

public static class Program
{
    public static unsafe void Main()
    {
        byte[] buf = new byte[4] { 1, 2, 3, 4 };

        // (2) fixed: 블록 동안 핀 고정, 포인터로 직접 쓰기
        fixed (byte* p = buf)
        {
            p[0] = 10;
            p[3] = 40;
            byte* q = p;
            Console.WriteLine(p == q);                 // 줄1
        }
        Console.WriteLine($"{buf[0]} {buf[3]}");        // 줄2

        // (3) GCHandle.Pinned: 더 오래 고정(interop/비동기 I/O)
        var h = GCHandle.Alloc(buf, GCHandleType.Pinned);
        try
        {
            IntPtr addr1 = h.AddrOfPinnedObject();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            IntPtr addr2 = h.AddrOfPinnedObject();
            Console.WriteLine(addr1 == addr2);          // 줄3
            Marshal.WriteByte(addr1, 1, 99);            // buf[1] = 99
        }
        finally { h.Free(); }

        Console.WriteLine(buf[1]);                       // 줄4
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

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