C++ 심화가 필요한 이유: 문법 다음에 만나는 실제 버그
1강. C++ 심화가 필요한 이유: 문법 다음에 만나는 실제 버그
이번 강의에서 해결할 문제
조건문과 클래스를 작성할 수 있어도 C++ 프로그램은 실행 중 갑자기 깨질 수 있습니다. 포인터가 가리키던 객체가 이미 사라졌거나, 같은 자원을 두 객체가 정리하거나, vector 변경 뒤 예전 주소를 사용하면 문법 오류 없이도 정의되지 않은 동작(Undefined Behavior, UB) 이 됩니다. 이번 강의는 이후 23개 강의에서 해결할 문제를 먼저 지도처럼 정리합니다.
학습 목표
먼저 알아야 할 핵심 개념
객체 수명은 객체를 사용할 수 있는 시간 구간이고, 소유권은 그 객체나 자원을 최종적으로 정리할 책임입니다. 비소유 관찰자는 잠시 주소나 참조를 사용하지만 파괴 책임은 없습니다. C++은 이 세 관계를 자동으로 모두 확인해 주지 않으므로 설계와 도구가 필요합니다.
| 문제 | 대표 증상 | 이후 해결 도구 |
|---|---|---|
| 댕글링 포인터 | 삭제된 객체를 다시 읽음 | 수명 분석, unique_ptr, ID 재조회 |
| 메모리 누수 | 반복할수록 메모리 증가 | RAII, 스마트 포인터 |
| 이중 해제 | 종료 또는 삭제 시 크래시 | 단일 소유권, Rule of Zero |
| iterator 무효화 | 컨테이너 변경 뒤 값이 깨짐 | 컨테이너 계약, 변경 뒤 재획득 |
| 데이터 레이스 | 실행할 때마다 결과가 달라짐 | 공유 상태 축소, mutex, ThreadSanitizer |
왜 이 문제가 위험한가
UB는 반드시 즉시 크래시하지 않습니다. Debug에서는 우연히 동작하고 Release에서만 실패할 수 있으며, 로그를 추가하면 메모리 배치가 달라져 증상이 사라질 수도 있습니다. “내 컴퓨터에서는 됐다”는 안전성 증거가 아닙니다. 입력, 빌드 설정, 실행 순서와 도구 보고서를 함께 남겨야 재현 가능한 수정이 됩니다.
가장 작은 예제로 시작하기
다음 프로그램은 안전한 값 반환과 위험한 주소 반환의 차이를 보여 줍니다. 위험한 함수는 호출하지 않고 코드 리뷰 대상으로만 둡니다.
#include <iostream>
#include <string>
std::string makeName() {
std::string name{"worker"};
return name; // 값을 반환한다.
}
const std::string* badNameAddress() {
std::string name{"worker"};
return &name; // 경고 대상: 함수가 끝나면 name이 파괴된다.
}
int main() {
const std::string name = makeName();
std::cout << name << '\n';
}
worker
코드 한 줄씩 이해하기
name은makeName블록 안에서 생성되는 지역 객체입니다.- 값 반환은 호출자에게 새로운
std::string결과를 전달합니다. C++17에서는 반환값 최적화가 불필요한 복사를 줄일 수 있습니다. badNameAddress의 포인터 값 자체는 주소이지만, 그 주소가 가리키던 지역 객체는 함수 종료와 함께 파괴됩니다.- 포인터가 null이 아니어도 대상 수명이 끝났다면 안전하지 않습니다.
- 높은 경고 수준은
badNameAddress같은 실수를 컴파일 단계에서 드러내는 첫 방어선입니다.
실행 결과와 메모리·동작 흐름
makeName 호출 중 지역 문자열이 생성되고, 반환 결과가 main의 name으로 완성된 뒤 함수 지역 객체의 책임은 끝납니다. 반면 지역 주소를 반환하면 호출자가 받은 숫자 형태의 주소는 남아도 사용할 수 있는 객체는 없습니다. 이후 그 주소를 역참조하면 결과를 예측할 수 없습니다.
단계별 실습
흔한 실수와 해결 방법
언제 사용하고 언제 피할까
값 반환은 결과의 소유권이 호출자로 넘어가는 일반적인 선택입니다. 비소유 포인터는 대상 수명이 호출보다 길고 null이 의미 있을 때만 사용합니다. 지역 객체 주소 반환, 근거 없는 수동 할당, 오래 저장하는 비소유 주소는 피합니다.
게임 개발 연결
게임, 서버, 도구 모두 프레임이나 요청이 끝난 뒤 남은 주소에서 같은 문제가 발생합니다. 엔진 API보다 먼저 표준 C++의 수명과 소유권 용어를 정확히 익혀야 합니다.
이해 점검 질문 3개
- non-null 포인터도 안전하지 않을 수 있는 이유는 무엇인가요?
- UB가 Debug에서는 보이지 않고 Release에서 나타날 수 있는 이유는 무엇인가요?
- 버그 수정 전에 최소 재현 입력과 빌드 설정을 기록해야 하는 이유는 무엇인가요?
핵심 요약
다음 강의 연결
2강. 스택·힙·정적 영역과 객체 수명에서 주소가 아니라 객체가 실제로 언제 생성되고 파괴되는지 추적합니다.
C++ 심화가 필요한 이유: 문법 다음에 만나는 실제 버그 미니 퀴즈
선택 즉시 정답과 해설을 확인할 수 있습니다. 결과는 이 브라우저에만 저장됩니다.
학습을 마쳤나요?
직접 실습과 점검 질문까지 확인한 뒤 완료로 표시하세요.