52. 대량 경험치 일괄 지급과 레벨업 계산 (C#)
난이도 중내 리뷰 · C#
내 리뷰 · C++
해설 · C#
해설 — 대량 경험치 일괄 지급과 레벨업 계산 (C#)
난이도: 중
요약
단일 처리 경로의 순수 계산 결함이다(동시성 아님). (A) 경험치를 32비트 int 로 누적해
대량 일괄 지급 시 오버플로가 나서 값이 음수로 손상되고, (B) 레벨업 루프가 최대 레벨을
클램프하지 않아 만렙을 넘어 성장하며 보상을 과다 지급한다(또는 오버플로와 맞물려 루프가
비정상 동작한다).
문제점
- (A) 정수 오버플로 (int 누적):
Exp와ExpToNext반환형이 모두int다. 오프라인 누적 보상처럼 큰amount(예: 20억 근처, 혹은 여러 번 누적)를 더하면c.Exp += amount가int.MaxValue(약 21.4억)를 넘어 음수로 래핑된다. 그러면c.Exp >= ExpToNext(Level)가 거짓이 되어 거대한 경험치를 받고도 레벨이 전혀 오르지 않는다(또는 손상된 값으로 엉뚱하게 오른다). C# 은 기본unchecked라 예외도 없이 조용히 래핑된다.- 재현: 오프라인 14일 보상으로 2,100,000,000 exp 일괄 지급.
- 근본 원인: 도메인 값의 범위를 넘는 타입 선택(누적/임계 계산에 int).
- (B) 최대 레벨 클램프 누락:
루프가
Level < MaxLevel을 확인하지 않아 만렙을 넘어 계속 오른다. 요구사항(초과분 폐기)을 위반해 스탯이 무한 인플레된다. 나아가 레벨이 커지면ExpToNext=100*level*level도int로 오버플로(예: level≈4632 이상에서 2^31 초과)해 임계가 음수가 되면 while 조건이 항상 참이 되어 폭주할 수 있다.- 근본 원인: "만렙에서 멈춘다"는 경계 규칙이 코드에 없음.
수정안
누적/임계 계산을 넉넉한 정수형(long)으로 올리고, 최대 레벨을 클램프하며 초과분을 폐기한다.
public sealed class Character
{
public int Level = 1;
public long Exp; // 넉넉한 폭
public int StatPoints;
public long ExpToNext(int level) => 100L * level * level; // long 산술
}
public void GrantExp(Character c, long amount)
{
if (amount <= 0) return;
c.Exp += amount; // long 누적(현실 범위에서 오버플로 없음)
while (c.Level < MaxLevel && c.Exp >= c.ExpToNext(c.Level))
{
c.Exp -= c.ExpToNext(c.Level);
c.Level++;
c.StatPoints += 5;
}
if (c.Level >= MaxLevel) c.Exp = 0; // 초과분 폐기
}
극단적으로 큰 입력까지 방어하려면 checked { c.Exp += amount; } 로 오버플로를 예외화하거나
상한으로 포화(saturate)시키는 정책을 둔다.
더 나은 설계
- 값 범위 명세: 경험치/재화 같은 누적값은 예상 최대치를 계산해 타입을 정하고, 필요하면
checked/포화 연산으로 경계를 강제. - 레벨/보상 테이블화: 레벨별 필요 경험치·보상을 데이터 테이블로 두고 만렙 캡을 상수로.
- 경계 테스트: 만렙 도달, 만렙 근처 대량 지급,
int경계 근처 입력을 유닛 테스트로 고정.
면접 포인트
- "조용한 정수 오버플로"(C# 기본 unchecked)가 만드는 논리 붕괴와
checked/long대응. - 누적값의 타입은 도메인 최대치로 정한다 — int 는 21억에서 끝난다.
- 만렙 클램프 같은 경계 규칙을 코드로 강제하고 경계 테스트로 고정.
해설 · C++
해설 — 대량 경험치 일괄 지급과 레벨업 계산 (C++)
난이도: 중
요약
단일 처리 경로의 순수 계산 결함이다(동시성 아님). (A) 경험치를 32비트 int 로 누적/
비교해 대량 일괄 지급 시 부호 있는 정수 오버플로(UB) 가 나고, (B) 레벨업 루프가
최대 레벨을 클램프하지 않아 만렙을 넘어 성장하며 expToNext 자체도 오버플로해 폭주할
수 있다.
문제점
- (A) signed int 오버플로 (UB):
exp와expToNext가int다. 오프라인 누적 보상처럼 큰amount(약 21.4억 근처)를 더하면c.exp += amount가INT_MAX를 넘어 부호 있는 오버플로 = 정의되지 않은 동작(UB) 이 된다. 실제로는 음수로 래핑되어 while 조건이 거짓이 되고, 거대한 경험치를 받고도 레벨이 오르지 않거나, 컴파일러 최적화에 따라 예측 불가한 동작이 나온다.- 재현: 오프라인 보상 2,100,000,000 exp 일괄 지급.
- 근본 원인: 도메인 값 범위를 넘는 타입 선택(누적/임계에 int).
- (B) 최대 레벨 클램프 누락 + expToNext 오버플로:
루프가
level < kMaxLevel을 확인하지 않아 만렙을 넘어 계속 오른다(스탯 인플레). 레벨이 커지면100*lv*lv도int로 오버플로(예: lv≈4632+ 에서 2^31 초과)해 임계가 음수가 되면 while 이 항상 참이 되어 무한/폭주 루프가 될 수 있다. 근본 원인은 경계 규칙 부재.
수정안
누적/임계를 int64_t 로 올리고, 최대 레벨을 클램프하며 초과분을 폐기한다.
#include <cstdint>
struct Character {
int level = 1;
int64_t exp = 0; // 넉넉한 폭
int statPoints = 0;
int64_t expToNext(int lv) const { return 100LL * lv * lv; } // long 산술
};
void grantExp(Character& c, int64_t amount) {
if (amount <= 0) return;
c.exp += amount; // int64_t 누적(현실 범위 안전)
while (c.level < kMaxLevel && c.exp >= c.expToNext(c.level)) {
c.exp -= c.expToNext(c.level);
c.level++;
c.statPoints += 5;
}
if (c.level >= kMaxLevel) c.exp = 0; // 초과분 폐기
}
극단 입력까지 막으려면 덧셈 전 오버플로 검사(amount > kExpCap - c.exp 면 포화) 또는
상한 포화 정책을 둔다.
더 나은 설계
- 값 범위 명세: 누적값은 예상 최대치로 타입을 정하고, 필요 시 오버플로 검사/포화.
- 레벨/보상 테이블화와 만렙 캡 상수화.
- 경계 테스트: 만렙 도달·대량 일괄 지급·정수 경계 입력을 유닛 테스트로 고정.
면접 포인트
- C++ 의 signed 오버플로는 UB — "음수로 래핑된다"에 의존하면 안 되고 타입/검사로 예방.
- 누적값 타입은 도메인 최대치로(int 는 21억에서 끝난다), 임계 계산도 승격 주의(
100LL). - 만렙 클램프 등 경계 규칙을 코드로 강제.