5. Span<T>/stackalloc 와 ref struct 의 스택 전용 제약

난이도 상 해설 보기 →

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

결함 코드 · C#
// ============================================================================
// [언어 내부동작 문제] C# - Span<T>/stackalloc 와 ref struct 의 스택 전용 제약
// ----------------------------------------------------------------------------
// 주제: Span<T> 가 왜 ref struct(스택 전용)인지, stackalloc 과 결합한 무할당(zero-alloc)
//       파싱, 그리고 ref struct 가 힙/비동기/클로저로 "탈출(escape)" 하지 못하는 이유.
//
// 아래는 패킷 헤더를 힙 할당 없이 읽는 파서다. 이어서 ref struct 제약을 시험하는 여러
// 코드 조각이 주석으로 제시된다.
//
// 과제(코드를 고치는 문제가 아니라, 컴파일 가부와 내부 동작을 예측·설명하는 문제):
//   1) ParseHeader 가 왜 힙 할당 없이 동작하는지(Span/stackalloc/슬라이싱) 설명하라.
//   2) 아래 (F1)~(F5) 각각이 컴파일되는지/안 되는지 예측하고, 그 이유를 ref struct 의
//      수명·탈출 규칙으로 설명하라.
//   3) Span 이 ref struct 가 아니었다면(일반 struct/class였다면) 어떤 안전성 문제가 생겼을지,
//      그리고 비동기 경로에서 무할당이 필요할 때 무엇을 쓰는지(Memory<T>) 설명하라.
//   (먼저 직접 예측을 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Buffers.Binary;
using System.Threading.Tasks;

public readonly ref struct Header   // ref struct: 스택 전용
{
    public readonly ushort Type;
    public readonly ushort Len;
    public Header(ushort type, ushort len) { Type = type; Len = len; }
}

public class Parser
{
    // 힙 할당 없이 헤더 + payload 슬라이스를 얻는다
    public Header ParseHeader(ReadOnlySpan<byte> buf, out ReadOnlySpan<byte> payload)
    {
        ushort type = BinaryPrimitives.ReadUInt16LittleEndian(buf);
        ushort len  = BinaryPrimitives.ReadUInt16LittleEndian(buf.Slice(2));
        payload = buf.Slice(4, len);          // 복사 없이 같은 메모리의 뷰
        return new Header(type, len);
    }

    public void Demo()
    {
        Span<byte> tmp = stackalloc byte[8];  // 스택 메모리, GC 무관
        tmp[0] = 1;
        // ... tmp 를 채워 ParseHeader 에 넘길 수 있음
    }

    // ── 아래 조각들이 컴파일되는지 예측하라(이유 포함) ──────────────────────────
    // (F1)  object o = (object)someSpan;                 // Span 을 object 로 박싱
    // (F2)  class Box { Span<byte> field; }              // Span 을 클래스 필드로
    // (F3)  Func<int> f = () => someSpan[0] + 1;         // Span 을 람다로 캡처
    // (F4)  async Task A() { Span<byte> s = stackalloc byte[4]; await Task.Yield(); s[0]=1; }
    // (F5)  Span<byte> s = stackalloc byte[4]; foreach (var x in list) { ... }  // await 없는 동기 사용
}
내 리뷰 · C#
내 답안 · 자동 저장

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