44. 인벤토리 자동 정렬과 슬롯 인덱스 명령의 TOCTOU (C#)
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: MMORPG 인벤토리 서버
//
// 인벤토리는 슬롯 배열이며, 클라이언트는 "N번 슬롯의 아이템을 사용/장착"처럼
// 슬롯 인덱스를 담아 요청을 보낸다. 동시에 서버는 "자동 정렬(빈칸 제거·종류별
// 재배치)" 기능을 제공한다. 정렬은 슬롯의 아이템 배치를 바꾼다.
//
// 요구사항:
// - 클라이언트가 지정한 슬롯의 "그" 아이템에 대해서만 사용/장착이 일어나야 한다.
// - 소비 아이템은 사용 시 수량이 정확히 1 감소하고, 0이 되면 슬롯이 비워진다.
// - 자동 정렬은 인벤토리 내용(어떤 아이템이 몇 개인지)을 절대 바꾸지 않는다.
// - 사용 요청과 정렬 요청은 서로 다른 클라이언트/타이머 스레드에서 동시에 들어온다.
//
// 아래는 그 처리기다. (A)(B) 지점을 검토하라.
using System;
using System.Collections.Generic;
using System.Linq;
public sealed class Item
{
public int ItemId;
public int Count;
public bool Consumable;
}
public sealed class Inventory
{
// 슬롯 배열: 빈 슬롯은 null.
private readonly Item[] _slots;
public Inventory(int size) { _slots = new Item[size]; }
// 클라이언트 요청 스레드에서 호출된다.
public void UseItem(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= _slots.Length)
throw new ArgumentOutOfRangeException(nameof(slotIndex));
Item item = _slots[slotIndex]; // (A)
if (item == null)
return; // 빈 슬롯
if (!item.Consumable)
{
Equip(item);
return;
}
item.Count -= 1;
ApplyConsumeEffect(item);
if (item.Count <= 0)
_slots[slotIndex] = null;
}
// 타이머/UI 스레드에서 "자동 정렬" 버튼으로 호출된다.
public void AutoSort()
{
// 빈칸을 앞으로 모으고 ItemId 순으로 재배치한다.
var packed = _slots.Where(s => s != null)
.OrderBy(s => s.ItemId)
.ToList(); // (B)
for (int i = 0; i < _slots.Length; i++)
_slots[i] = i < packed.Count ? packed[i] : null;
}
private void Equip(Item item) { /* 장착 처리 */ }
private void ApplyConsumeEffect(Item item) { /* 소비 효과 적용 */ }
} 결함 코드 · C++
// 시나리오: MMORPG 인벤토리 서버 (C++)
//
// 인벤토리는 슬롯 벡터이며, 클라이언트는 슬롯 인덱스로 "그 슬롯의 아이템을 사용"을
// 요청한다. 동시에 서버는 자동 정렬(빈칸 제거·ItemId 순 재배치)을 제공한다.
//
// 요구사항:
// - 클라이언트가 지정한 슬롯의 "그" 아이템에 대해서만 사용/장착이 일어나야 한다.
// - 소비 아이템은 사용 시 수량이 1 감소하고, 0이 되면 슬롯이 비워진다.
// - 자동 정렬은 인벤토리 내용을 바꾸지 않는다.
// - 사용 요청과 정렬 요청은 서로 다른 스레드에서 동시에 들어온다.
//
// 아래 처리기의 (A)(B) 지점을 검토하라.
#include <vector>
#include <algorithm>
#include <memory>
struct Item {
int itemId;
int count;
bool consumable;
};
class Inventory {
public:
explicit Inventory(std::size_t size) : slots_(size) {}
// 클라이언트 요청 스레드에서 호출.
void useItem(std::size_t slotIndex) {
if (slotIndex >= slots_.size()) return;
Item* item = slots_[slotIndex].get(); // (A)
if (item == nullptr) return;
if (!item->consumable) { equip(item); return; }
item->count -= 1;
applyConsumeEffect(item);
if (item->count <= 0)
slots_[slotIndex].reset();
}
// 타이머/UI 스레드에서 "자동 정렬"로 호출.
void autoSort() {
std::vector<std::unique_ptr<Item>> packed;
for (auto& s : slots_)
if (s) packed.push_back(std::move(s));
std::sort(packed.begin(), packed.end(),
[](const auto& a, const auto& b){ return a->itemId < b->itemId; });
for (std::size_t i = 0; i < slots_.size(); ++i) // (B)
slots_[i] = (i < packed.size()) ? std::move(packed[i]) : nullptr;
}
private:
std::vector<std::unique_ptr<Item>> slots_;
void equip(Item*) {}
void applyConsumeEffect(Item*) {}
};
int main() { Inventory inv(32); (void)inv; return 0; } 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.