디버깅 기초 (Debugging)

Jaemyeong Lee·2024년 8월 3일

게임 서버1

목록 보기
24/220

이 Step에서 다루는 것

  • “왜 디버깅이 필수 스킬인지” (현업 관점)
  • 브레이크포인트로 흐름을 멈추고, 변수/메모리를 증거로 확인하는 방법
  • 조건부/로그 출력(Tracepoint)으로 “멈추지 않고 추적”하는 방법
  • Call Stack으로 “어디서 왔는지”를 역추적하는 방법

학습 목표

  • 버그를 눈으로 찾지 않고, 재현 → 가설 → 중단점 → 관찰 → 수정 → 재검증 루틴으로 해결할 수 있다.
  • F9/F5/F10/F11/Shift+F11/Ctrl+F5 단축키의 의미를 정확히 구분한다.

디버깅의 중요성

  • 개발·디버깅 비율이 5:5에 가까울 정도로 중요합니다.
  • 코드가 커지면(예: 수십만 줄) “전체를 읽어서” 원인을 찾는 건 현실적으로 불가능합니다.
  • 그래서 디버깅은 다음 사고방식으로 접근합니다.
    • 의심 구간에 멈춘다
    • 값과 흐름을 증거로 확인한다
    • 원인을 좁혀간다

예를 들어 “퀘스트를 깼는데 업적 점수가 안 오른다” 같은 버그가 올라오면,
모든 퀘스트/업적 코드를 전부 읽는 게 아니라, 업적이 갱신되는 지점(또는 의심 구간)에 중단점을 걸고
“정말 그 경로로 들어오는지, 변수 값이 무엇인지”를 먼저 확인합니다.


브레이크포인트 (Breakpoint)

조작방법
설정/해제줄 왼쪽 회색 여백 클릭 또는 F9
표시빨간 동그라미
  • 실행이 해당 줄에 도달하면 멈춥니다.
  • 멈춘 상태에서 가능한 일:
    • 변수 값 확인: Locals/Watch/QuickWatch 등
    • 값 변경: 변수 값을 임시로 바꿔서 “이 값이면 버그가 재현/해결되는지” 실험
    • 흐름 제어: 한 줄씩 실행하거나(다음 섹션), 호출 흐름을 따라가며 원인 추적

주의: “실행 줄을 강제로 옮기는 기능(Set Next Statement)”은 강력하지만,
프로그램 상태를 비정상으로 만들 수 있어 마지막 수단으로만 쓰는 게 안전합니다.


조건부 브레이크포인트

  • 브레이크포인트 우클릭 → 조건(Condition) 으로 “특정 상황에서만 멈추기”가 가능합니다.
  • 예: hp <= 0 → 죽는 순간에만 정지
  • 대규모 코드(특히 MMO)에서는 “같은 코드가 여러 대상에게 반복 실행”되므로, 조건부 중단점이 매우 강력합니다.
    • 예: “몬스터 ID가 4000일 때만 멈춰라”

추가로 자주 쓰는 옵션들:

  • Hit Count(적중 횟수): N번째 실행에서만 멈추기
  • Filter(필터): 스레드/프로세스 등 조건으로 제한 (상황에 따라)

로컬(Local) 창과 조사식(Watch)

역할
로컬현재 함수에 선언된 변수들의 값 자동 표시
조사식hp + 200, (hp > 50) ? "OK" : "FAIL"수식 입력 가능
  • 조사식은 “값을 보는 창”이 아니라, 가설을 즉석에서 계산/검증하는 도구입니다.
    • 예: hp + 200이 정말 210이 맞는지 확인
    • 예: (hp <= 0)가 지금 참인지 확인

팁:

  • Locals에서 변수를 우클릭해서 Watch에 추가하면 빠릅니다.
  • 수식이 복잡해지면 Watch에 “의미 있는 이름”처럼 남겨두고 반복 확인합니다.

코드 실행 제어 (F10 / F11 / Shift+F11)

단축키동작
F5 (Continue)다음 중단점까지 계속 실행
F10 (Step Over)현재 줄 실행, 함수 안으로 들어가지 않음
F11 (Step Into)함수 내부로 진입
Shift+F11현재 함수 끝까지 실행 후 호출 지점으로 복귀
Ctrl+F10Run to Cursor (커서 위치까지 실행)
Ctrl+Shift+F5Restart (디버깅 재시작)
Shift+F5Stop (디버깅 중지)
Ctrl+F5Start Without Debugging (디버깅 없이 실행)

핵심 정리: Ctrl+F5는 “중단점 무시”가 아니라 디버깅 자체를 붙이지 않고 실행입니다.
디버깅을 붙여서(F5) 실행해야 브레이크포인트/콜스택/워치가 제대로 동작합니다.


호출 스택 (Call Stack)

  • 현재까지 어떤 함수들이 호출됐는지 순서로 표시.
  • 예시(위가 현재 함수, 아래가 호출자):
    GameCoding.exe!Test3() 줄 16
    GameCoding.exe!Test2() 줄 12
    GameCoding.exe!Test()  줄 8
    GameCoding.exe!main()  줄 29
  • “왜 여기로 왔지?”에 대한 가장 빠른 답이 Call Stack입니다.
  • 함수 흐름 오류(잘못된 순서, 예상치 못한 경로, 무한 호출 등) 파악에 매우 유용합니다.

출력 로그 (디버그 시)

멈추면 흐름이 깨지거나(실시간 게임 루프), 너무 자주 멈추는 구간이라면 “멈추지 않는 중단점”을 씁니다.

  • 브레이크포인트 우클릭 → Actions(작업) 에서
    • 메시지 출력(로그) 만 하고
    • 계속 실행(Continue) 하도록 설정할 수 있습니다. (Tracepoint)

예:

  • "EnterBattle: hp={hp}, damage={damage}"
    → 출력만 하고 실행은 계속 (중단하지 않음)

이 방식은 “코드가 지나가긴 하는지/몇 번 지나가는지/변수 값이 어떻게 변하는지”를 추적할 때 매우 유용합니다.


디버깅 마인드셋

  • 눈으로만 버그 찾기 X → 실행 + 중단점 + 값 확인이 기본입니다.
  • “강의랑 똑같이 쳤는데 안 돼요”는, 대부분 코드가 “다른 값을 갖고/다른 경로로 실행”되기 때문입니다.
    눈으로 비교하기보다 디버깅으로 값·흐름을 확인하세요.

실전 루틴 (step-by-step)

  1. 재현: 어떤 입력/상황에서 문제가 나는지 고정
  2. 가설: “이 값이 이상할 것이다 / 이 경로로 들어올 것이다” 1~2개 세우기
  3. 중단점: 가설을 검증할 지점에 브레이크포인트(필요하면 조건/히트카운트)
  4. 관찰: Locals/Watch/Call Stack로 값과 흐름을 증거로 확인
  5. 수정: 가장 작은 수정으로 원인을 제거
  6. 재검증: 같은 시나리오 + 변형 시나리오로 다시 확인

C++은 메모리/수명(스택/힙) 실수로 크래시가 자주 나기 때문에,
“스택 프레임/호출 스택/수명”을 이해한 디버깅이 특히 강력합니다.


미니 실습 1: “값이 왜 안 바뀌지?”를 디버깅으로 증명하기

목표: “복사 전달” 때문에 원본이 안 바뀐다는 걸 눈이 아니라 증거(값/콜스택)로 확인합니다.

void AddHp(int hp, int value)
{
    hp += value;
}

int main()
{
    int hp = 100;
    AddHp(hp, 20);
    return 0;
}

step-by-step:

  1. AddHp(hp, 20); 줄에 브레이크포인트(F9)
  2. 디버깅 시작(F5) → 멈추면 hp를 Locals에서 확인 (100)
  3. F11(Step Into)AddHp 내부로 들어가기
  4. hp += value; 실행 전/후로 hp 값을 확인
  5. Shift+F11(Step Out)main으로 복귀 후, hp가 여전히 100임을 확인

핵심 질문:

  • AddHp 안의 hp는 “main의 hp”인가, 아니면 “복사본”인가?

미니 실습 2: 조건부 브레이크포인트로 “죽는 순간만” 잡기

목표: 반복 루프에서 “원하는 순간”에만 멈추는 감각을 익힙니다.

int main()
{
    int hp = 100;
    for (int i = 0; i < 10; i++)
    {
        hp -= 13;
        // 여기에서 "hp <= 0"일 때만 멈추게 만들기
    }
    return 0;
}

step-by-step:

  1. 주석 줄에 브레이크포인트 설정
  2. 브레이크포인트 우클릭 → 조건(Condition) → hp <= 0
  3. F5로 실행 → “죽는 순간”에만 멈추는지 확인
  4. Watch에 i, hp를 올려서 “몇 번째 반복에서 죽는지” 확인

미니 실습 3: Tracepoint(멈추지 않는 로그)로 흐름 추적하기

목표: “멈추면 흐름이 깨지는 구간(루프)”에서, 로그로만 추적하는 방법을 익힙니다.

int main()
{
    int hp = 100;
    for (int i = 0; i < 5; i++)
    {
        hp -= 7;
        // 여기: i와 hp를 출력 창에 찍되, 멈추지 않게 설정
    }
    return 0;
}

설정 힌트:

  • 브레이크포인트 우클릭 → Actions(작업)
    • 메시지 예: i={i}, hp={hp}
    • “계속 실행(Continue)” 체크

디버깅 체크리스트 (버그가 안 잡힐 때)

  • 재현이 되나?: 재현 조건이 불안정하면 디버깅도 불안정합니다.
  • “멈춰야 할 지점”이 맞나?: 더 상위(원인에 가까운) 지점으로 이동해보기
  • 조건을 걸었나?: 반복 호출이 많으면 조건/히트카운트 없이는 잡기 어렵습니다.
  • Call Stack을 봤나?: “왜 여기로 왔는지”를 먼저 확인
  • 값을 바꿔 실험했나?: 원인을 좁히기 위해 변수를 임시로 조작해보기

profile
李家네_공부방

0개의 댓글