← 문제로

35. checked / unchecked 컨텍스트와 정수 오버플로의 내부 동작

난이도 중
내 리뷰 · C#
해설 · C#

해설 — checked / unchecked 컨텍스트와 정수 오버플로의 내부 동작

난이도: 중

요약

C#의 정수 오버플로는 기본적으로 unchecked(감싸기, wrap) 이다(프로젝트 옵션으로 바꾸지 않는 한). checked는 오버플로 시 OverflowException을 던진다. 단 컴파일 시점에 계산되는 상수식은 기본이 checked 라서, 상수 오버플로는 컴파일 에러가 된다. 골드·재화 같은 값은 조용한 wrap이 치명적이므로 checked 또는 더 넓은 타입 + 범위 검증으로 보호해야 한다.

핵심 개념 1: 런타임 오버플로 (RuntimeOverflow 출력)

unchecked: -2147483648      // (A) int.MaxValue + 1 이 감싸여 int.MinValue
checked: OverflowException   // (B) checked 는 예외
  • (A) 기본 컨텍스트에서 max + 1은 2의 보수 연산이 그대로 감싸여 int.MinValue(-2147483648)가 된다. 예외 없음.
  • (B) checked(...) 안에서는 오버플로가 감지되어 OverflowException.
  • IL 수준 차이: 일반 덧셈은 add 명령으로 컴파일된다. checked이면 add.ovf (overflow-checking add)로 컴파일되어, CPU의 오버플로 플래그를 확인하고 넘치면 예외를 던진다. 즉 checked/unchecked는 어떤 IL 산술 명령을 내보내는지의 차이다 (add/add.ovf, mul/mul.ovf, conv/conv.ovf). 런타임 비용은 분기 하나 수준으로 작다.

핵심 개념 2: 상수식은 기본이 checked (CompileTimeConst)

  • (C) const int a = int.MaxValue + 1; → 컴파일 에러. 컴파일 타임에 평가되는 상수식은 기본적으로 checked로 취급된다(언어 명세). 오버플로가 확정적이므로 컴파일러가 CS0220 "operation overflows at compile time"으로 거부한다.
  • (D) const int b = unchecked(int.MaxValue + 1); → 컴파일 성공. 명시적으로 unchecked를 씌우면 감싸기를 허용해 b == int.MinValue로 컴파일된다.
  • 요점: 런타임 산술의 기본은 unchecked, 컴파일타임 상수의 기본은 checked 로 방향이 반대다. 이 비대칭이 헷갈림의 핵심.

핵심 개념 3: 전역 옵션과 지역 컨텍스트

  • 프로젝트의 <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow> (csc의 /checked+)를 켜면 모든 비상수 산술의 기본이 checked가 된다. 성능 민감 루프만 국소적으로 unchecked { ... }로 되돌린다.
  • 지역 checked/unchecked 블록·식은 항상 전역 옵션을 국소적으로 오버라이드한다. 가장 안쪽 컨텍스트가 이긴다.
  • (E) AddGold의 문제: current + deltaint + intint로 계산된 뒤 long으로 승격된다. 즉 int 범위에서 먼저 오버플로가 나고(감싸짐) 그 감싼 값이 long이 된다. 반환 타입이 long이어도 소용없다.
    public static long AddGold(int current, int delta)
    {
        return (long)current + delta;   // long 산술로 승격 후 더함 → int 오버플로 없음
        // 또는 checked((int)...) 로 상한 초과를 예외/클램프 처리
    }
    

흔한 오해·함정

  • "C#은 오버플로하면 예외" → 기본은 아니다. 감싸기다. Java와 같은 기본 동작. 예외를 원하면 checked 또는 전역 옵션.
  • "반환 타입을 long으로 하면 안전" → 피연산자 타입이 int면 연산이 int로 먼저 일어나 그 시점에 오버플로. 승격은 연산 가 아니라 피연산자를 미리 넓혀야 한다.
  • "상수라서 wrap될 것" → 상수식은 기본 checked라 오히려 컴파일 에러. 방향이 반대.
  • decimal은 항상 오버플로 시 예외(checked/unchecked 무관). float/double은 오버플로가 아니라 Infinity가 된다(정수 규칙과 다름).

면접 포인트

  • "C#의 기본 오버플로 동작은?" → 런타임 산술 기본 unchecked(wrap), 컴파일타임 상수식 기본 checked(오버플로면 컴파일 에러). 비대칭을 정확히 말할 것.
  • "checked의 IL/비용은?" → add.ovf/mul.ovf/conv.ovf 명령으로 오버플로 플래그 검사 + 예외. 비용은 작고, 재화·시퀀스 등 정확성이 중요한 곳엔 켜는 게 안전.
  • "재화 합산 안전하게?" → 피연산자를 미리 long으로 넓히거나 checked로 상한 초과를 탐지해 예외/클램프. int 범위 wrap이 재화 복사·소멸로 직결되기 때문.