이론편: 버그 유형 개요

Jaemyeong Lee·2024년 10월 30일

게임 서버1

목록 보기
61/220

이 Part에서 다루는 것

  • 디버깅 파트를 “동적 할당 이후”에 배우는 이유
  • 실무에서 자주 만나는 버그 4종
    • Null 크래시
    • 정수 오버플로우/언더플로우
    • 메모리 누수
    • 메모리 오염(UAF/버퍼 오버플로우/잘못된 캐스팅)
  • 크래시형 버그와 오동작형 버그의 추적 방식 차이
  • 재현 중심 디버깅 루프(가설 -> 검증 -> 수정 -> 회귀 확인)

학습 목표

  • 버그 증상만 보고도 우선 의심할 범주를 빠르게 좁힐 수 있다.
  • “왜 이 포인터가 nullptr인가?”, “왜 이 값이 범위를 벗어났나?”를 역추적할 수 있다.
  • 재현이 어려운 버그에서 로그/계측을 어떻게 설계할지 설명할 수 있다.

왜 이 시점에 디버깅을 배우나

  • 동적 할당(new/delete), 포인터, 상속이 섞이기 시작하면
    버그가 “컴파일 에러”보다 “런타임 사고” 형태로 많이 나타납니다.
  • 즉, 문법을 아는 것만으로는 부족하고
    실행 중 상태를 추적하는 능력이 필수입니다.

버그 유형 분류표(우선순위 관점)

빈번도(실무 체감)버그 유형대표 증상1차 대응
매우 높음Null 크래시특정 줄에서 즉시 크래시포인터 null 여부, 호출 스택 역추적
중간정수 오버플로우/언더플로우값이 갑자기 음수/비정상범위 체크, 타입 확장, 계산식 검증
중간메모리 누수메모리 사용량이 계속 상승할당/해제 짝 점검, 소유권 점검
낮지만 치명적메모리 오염랜덤 크래시/지연 크래시UAF/범위초과/잘못된 캐스팅 의심

크래시 vs 오동작

  • 크래시형: 바로 종료되므로 “터진 지점”은 분명한 편
  • 오동작형: 프로그램은 살아 있으나 결과가 틀림 (추적이 더 어려운 경우가 많음)

Null 크래시(가장 흔한 사고)

정의

  • null 포인터(nullptr)를 역참조할 때 발생하는 크래시

대표 시나리오

Player* FindPlayer(int id)
{
    // ... 검색 ...
    return nullptr; // 못 찾으면
}

Player* p = FindPlayer(100);
p->hp = 100; // ❌ p가 nullptr이면 크래시

핵심:

  • “못 찾음”을 nullptr로 표현하는 함수는 매우 많습니다.
  • 호출자 쪽에서 null 체크를 하지 않으면 크래시가 납니다.
void Test(Player* p)
{
    if (p == nullptr)
        return;

    p->hp = 10;
}

추적 루틴(실전)

  • 크래시 줄 확인
  • 해당 포인터가 왜 null인지 호출 스택 역추적
  • “누가 null을 넣었는지” 또는 “왜 생성 실패/조회 실패했는지” 원인 고정

정수 오버플로우·언더플로우

개념

  • 정수 타입의 표현 범위를 벗어나면 값이 깨집니다.
  • 특히 게임 로직(HP/골드/데미지 누적)에서 자주 숨어듭니다.

중요한 정확성:

  • unsigned 오버플로우는 모듈러 래핑이 정의됨
  • signed 오버플로우는 C++에서 UB(정의되지 않은 동작)
    (실무에서는 래핑처럼 보이는 경우가 많아도 “보장”은 아님)

자주 터지는 패턴

  • int * int 곱셈 누적
  • 루프 누적 합
  • 체력/경험치 상한 미보정

방어 습관

  • 상/하한 클램프 (0 ~ maxHp)
  • 필요 시 더 큰 타입(long long, std::int64_t)
  • 곱셈 전 캐스팅/범위 검증

메모리 누수(Memory Leak)

정의

  • 할당된 메모리가 해제되지 않아 회수 불가능한 상태로 남는 현상

증상

  • 메모리 사용량이 시간에 따라 계속 상승
  • 장시간 실행 후 성능 저하 -> OOM/크래시

감지 힌트

  • 메모리 그래프의 우상향 추세
  • 장시간/반복 시나리오(스폰, 로딩, 전투 반복)에서 가속
  • 32비트 환경 테스트로 조기 재현을 유도하는 방법도 실무에서 자주 사용

기본 대응

  • new/delete 짝 점검
  • 소유권(누가 해제?) 계약 점검
  • 부모 포인터 삭제 구조면 virtual 소멸자 확인

메모리 오염(Memory Corruption)

정의

  • 잘못된 메모리 접근으로 데이터가 조용히 손상되는 현상
  • “문제 발생 시점”과 “터지는 시점”이 달라 추적이 매우 어렵습니다.

대표 3종

  • Use-After-Free (UAF)
    삭제된 객체를 계속 참조해서 접근
  • 버퍼 오버플로우
    할당 범위를 넘는 인덱스/복사
  • 잘못된 캐스팅
    관계 없는 타입으로 강제 변환해 메모리 해석을 깨뜨림

주의할 점

  • delete p; p = nullptr;는 “p 하나”만 안전해집니다.
  • 같은 대상을 가리키던 다른 포인터(alias)는 여전히 댕글링일 수 있습니다.

디버깅 방법론: 눈대중이 아니라 실험

핵심 루프

  • 재현
  • 가설 수립
  • 계측/브레이크포인트로 검증
  • 수정
  • 회귀 확인(비슷한 경로까지 재검증)

왜 중요한가

  • 좋은 개발자는 “버그를 안 만드는 사람”보다
    “버그를 빨리 좁히고 정확히 고치는 사람”에 가깝습니다.

재현이 어려운 버그 대응

  • 발생 조건(맵/인원/시간/입력)을 로그로 구조화
  • 랜덤 시드는 고정해서 재실행 가능하게 만들기
  • “한 달에 한 번” 이슈는 샘플 부족이 핵심이므로
    계측 지점을 늘려서 확률을 데이터로 바꿔야 합니다.

profile
李家네_공부방

0개의 댓글