C++ 초보자가 자주 하는 실수 Top 15 | 컴파일 에러부터 런타임 크래시까지

C++ 초보자가 자주 하는 실수 Top 15 | 컴파일 에러부터 런타임 크래시까지

이 글의 핵심

C++ 초보자가 자주 하는 실수 Top 15에 대한 실전 가이드입니다. 컴파일 에러부터 런타임 크래시까지 등을 예제와 함께 상세히 설명합니다.

들어가며: “C++ 시작했는데 에러만 100개…"

"Hello World도 컴파일이 안 돼요”

C++를 처음 배우는 사람들이 반드시 겪는 실수들이 있습니다. 세미콜론 하나, 헤더 파일 하나 빠뜨려도 수십 줄의 에러 메시지가 쏟아집니다. 이 글은 C++ 입문자가 가장 자주 하는 실수 15가지를 정리하고, 각각의 에러 메시지와 해결법을 알려드립니다.

이 글을 읽으면:

  • 컴파일 에러 메시지를 읽고 5분 안에 해결할 수 있습니다
  • 같은 실수를 반복하지 않게 됩니다
  • C++ 문법의 핵심 규칙을 이해하게 됩니다

목차

  1. 컴파일 에러 Top 8
  2. 런타임 에러 Top 4
  3. 논리 에러 Top 3
  4. 에러 메시지 읽는 법
  5. 정리

1. 컴파일 에러 Top 8

실수 1: 세미콜론 누락

가장 흔한 실수: 클래스/구조체 정의 끝에 세미콜론을 빼먹음.

// ❌ 에러 코드
class MyClass {
    int x;
}  // ← 세미콜론 없음!

int main() {
    return 0;
}

// error: expected ';' after class definition

해결:

// ✅ 올바른 코드
class MyClass {
    int x;
};  // ← 세미콜론 필수!

주의사항: 클래스 안의 중첩 클래스·friend 선언 위치를 바꿀 때 세미콜론 위치가 흔들리기 쉽습니다.

주의: 함수 정의 끝에는 세미콜론이 없어야 합니다.

void foo() {
    // ...
}  // ← 세미콜론 없음 (올바름)

실수 2: 헤더 파일 미포함

// ❌ 에러 코드
int main() {
    cout << "Hello" << endl;  // cout이 뭔지 모름
    return 0;
}

// error: 'cout' was not declared in this scope
// error: 'endl' was not declared in this scope

해결:

// ✅ 올바른 코드
#include <iostream>

int main() {
    std::cout << "Hello" << std::endl;
    return 0;
}

주의사항: using namespace std;는 교육용 예제에는 편하지만, 실무 코드베이스에서는 이름 충돌을 피하기 위해 지양하는 편입니다.

자주 빠뜨리는 헤더:

기능필요한 헤더
cout, cin<iostream>
string<string>
vector<vector>
sqrt, pow<cmath>
sort, find<algorithm>

실수 3: using namespace std 없이 std:: 생략

// ❌ 에러 코드
#include <iostream>

int main() {
    cout << "Hello" << endl;  // std:: 없음
    return 0;
}

// error: 'cout' was not declared in this scope

해결법 1: std:: 접두사 사용 (권장)

// ✅ 올바른 코드
#include <iostream>

int main() {
    std::cout << "Hello" << std::endl;
    return 0;
}

해결법 2: using namespace std

// ✅ 동작하지만 권장하지 않음
#include <iostream>
using namespace std;

int main() {
    cout << "Hello" << endl;
    return 0;
}

주의: using namespace std;는 이름 충돌을 일으킬 수 있으므로 헤더 파일에는 절대 쓰지 마세요.

실수 4: main 함수 반환 타입 오류

// ❌ 에러 코드
void main() {  // ❌ void는 표준이 아님
    std::cout << "Hello\n";
}

// error: 'main' must return 'int'

해결:

// ✅ 올바른 코드
int main() {
    std::cout << "Hello\n";
    return 0;
}

실수 5: 변수 선언 위치 오류

// ❌ C++03 이전 스타일
int main() {
    for (int i = 0; i < 10; ++i) {
        // ...
    }
    
    std::cout << i << '\n';  // ❌ i는 for 블록 밖에서 접근 불가
}

// error: 'i' was not declared in this scope

해결:

// ✅ 올바른 코드
int main() {
    int i;  // 밖에서 선언
    for (i = 0; i < 10; ++i) {
        // ...
    }
    
    std::cout << i << '\n';  // 10
}

실수 6: const 불일치

// ❌ 에러 코드
void print(std::string& s) {  // 비const 참조
    std::cout << s << '\n';
}

int main() {
    print("Hello");  // 임시 객체는 비const 참조에 바인딩 불가
}

// error: cannot bind non-const lvalue reference of type 'std::string&' 
//        to an rvalue of type 'std::string'

해결:

// ✅ 올바른 코드
void print(const std::string& s) {  // const 참조
    std::cout << s << '\n';
}

int main() {
    print("Hello");  // OK
}

실수 7: 배열 초기화 오류

// ❌ 에러 코드
int arr[5];
arr = {1, 2, 3, 4, 5};  // ❌ 선언 후에는 이렇게 할당 불가

// error: invalid array assignment

해결:

// ✅ 올바른 코드
int arr[5] = {1, 2, 3, 4, 5};  // 선언과 동시에 초기화

// 또는
int arr[5];
for (int i = 0; i < 5; ++i) {
    arr[i] = i + 1;
}

// 또는 C++11
int arr[] = {1, 2, 3, 4, 5};  // 크기 자동 추론

실수 8: 함수 선언과 정의 불일치

// ❌ 에러 코드
// header.h
void foo(int x);

// main.cpp
void foo(double x) {  // ❌ 타입이 다름
    // ...
}

int main() {
    foo(42);
}

// error: undefined reference to 'foo(int)'

해결:

// ✅ 올바른 코드
// header.h
void foo(int x);

// main.cpp
void foo(int x) {  // 타입 일치
    // ...
}

2. 런타임 에러 Top 4

실수 9: 포인터 초기화 안 함

// ❌ 크래시 코드
int main() {
    int* ptr;  // 초기화 안 함 (쓰레기 값)
    *ptr = 42;  // ❌ 임의의 메모리에 쓰기 → 크래시
}

// Segmentation fault

해결:

// ✅ 올바른 코드
int main() {
    int* ptr = nullptr;  // 초기화
    
    if (ptr == nullptr) {
        ptr = new int(42);
    }
    
    delete ptr;
}

// ✅ 더 좋은 방법
int main() {
    auto ptr = std::make_unique<int>(42);
    // 자동 해제
}

실수 10: 배열 범위 초과

// ❌ 크래시 코드
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    for (int i = 0; i <= 5; ++i) {  // ❌ i=5는 범위 밖
        std::cout << arr[i] << '\n';
    }
}

// 미정의 동작 (쓰레기 값 또는 크래시)

해결:

// ✅ 올바른 코드
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    for (int i = 0; i < 5; ++i) {  // i < 5
        std::cout << arr[i] << '\n';
    }
}

// ✅ 더 안전한 방법: 범위 기반 for
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    for (int x : arr) {
        std::cout << x << '\n';
    }
}

실수 11: 문자열 비교를 == 로

// ❌ 잘못된 비교
int main() {
    char str1[] = "hello";
    char str2[] = "hello";
    
    if (str1 == str2) {  // ❌ 주소 비교 (항상 false)
        std::cout << "Same\n";
    } else {
        std::cout << "Different\n";  // 이게 출력됨
    }
}

해결:

// ✅ C 스타일 문자열
#include <cstring>

int main() {
    char str1[] = "hello";
    char str2[] = "hello";
    
    if (strcmp(str1, str2) == 0) {  // 내용 비교
        std::cout << "Same\n";
    }
}

// ✅ C++ std::string (권장)
#include <string>

int main() {
    std::string str1 = "hello";
    std::string str2 = "hello";
    
    if (str1 == str2) {  // OK
        std::cout << "Same\n";
    }
}

실수 12: 지역 변수 주소 반환

// ❌ 댕글링 포인터
int* createNumber() {
    int x = 42;
    return &x;  // ❌ 지역 변수 주소 반환
}  // x는 소멸됨

int main() {
    int* ptr = createNumber();
    std::cout << *ptr << '\n';  // ❌ 미정의 동작
}

// warning: address of local variable 'x' returned

해결:

// ✅ 해결 1: 힙 할당
int* createNumber() {
    return new int(42);  // 호출자가 delete 해야 함
}

// ✅ 해결 2: 스마트 포인터 (권장)
std::unique_ptr<int> createNumber() {
    return std::make_unique<int>(42);
}

// ✅ 해결 3: 값 반환
int createNumber() {
    return 42;
}

3. 논리 에러 Top 3

실수 13: = 와 == 혼동

// ❌ 버그 (컴파일은 됨)
int main() {
    int x = 5;
    
    if (x = 10) {  // ❌ 대입 연산자 (항상 true)
        std::cout << "x is 10\n";  // 항상 실행됨
    }
    
    std::cout << "x = " << x << '\n';  // x = 10
}

// warning: using the result of an assignment as a condition without parentheses

해결:

// ✅ 올바른 코드
int main() {
    int x = 5;
    
    if (x == 10) {  // 비교 연산자
        std::cout << "x is 10\n";
    }
}

// 팁: 상수를 왼쪽에 두면 실수 방지
if (10 == x) {  // 실수로 10 = x 쓰면 컴파일 에러
    // ...
}

실수 14: 정수 나눗셈

// ❌ 버그
int main() {
    int a = 5;
    int b = 2;
    
    double result = a / b;  // ❌ 정수 나눗셈 → 2.0
    std::cout << result << '\n';  // 2 (2.5가 아님!)
}

해결:

// ✅ 올바른 코드
int main() {
    int a = 5;
    int b = 2;
    
    double result = static_cast<double>(a) / b;  // 2.5
    std::cout << result << '\n';
}

// 또는
double result = a / static_cast<double>(b);

// 또는
double result = static_cast<double>(a) / static_cast<double>(b);

실수 15: 부호 없는 정수 언더플로우

// ❌ 버그
int main() {
    unsigned int x = 5;
    unsigned int y = 10;
    
    unsigned int diff = x - y;  // ❌ 음수가 될 수 없음 → 큰 양수
    std::cout << diff << '\n';  // 4294967291 (2^32 - 5)
}

해결:

// ✅ 올바른 코드
int main() {
    int x = 5;  // 부호 있는 정수
    int y = 10;
    
    int diff = x - y;  // -5
    std::cout << diff << '\n';
}

// 또는 조건 확인
unsigned int x = 5, y = 10;
if (x > y) {
    unsigned int diff = x - y;
} else {
    // x <= y인 경우 처리
}

4. 에러 메시지 읽는 법

컴파일 에러 메시지 구조

main.cpp:10:5: error: 'cout' was not declared in this scope
^^^^^^^  ^^  ^^ ^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
파일     줄  열 타입    에러 내용

   10 |     cout << "Hello" << endl;
      |     ^~~~
      |     std::cout

읽는 순서:

  1. 파일:줄: main.cpp:10 - 어느 파일의 몇 번째 줄
  2. 에러 타입: error (컴파일 실패) vs warning (경고)
  3. 에러 내용: 'cout' was not declared - 무엇이 문제인지
  4. 제안: std::cout - 어떻게 고칠지

자주 나오는 에러 메시지 패턴

에러 메시지의미해결법
expected ';' before세미콜론 누락이전 줄 끝에 ; 추가
was not declared in this scope변수/함수를 찾을 수 없음선언 추가 또는 헤더 포함
no matching function함수 오버로드를 찾지 못함인자 타입·개수 확인
cannot convert타입 변환 불가타입 일치시키기 또는 캐스팅
undefined reference링커 에러 (정의 없음)소스 파일 추가 또는 라이브러리 링크
invalid use of incomplete type전방 선언만 있음헤더 포함
redefinition of중복 정의헤더 가드 추가

더 많은 실수 패턴

실수 16: cin으로 문자열 입력 시 공백 처리

cin >> 연산자공백(스페이스, 탭, 엔터)을 구분자로 사용합니다. 따라서 공백이 포함된 문자열을 입력받을 수 없습니다.

// ❌ 공백 이후 잘림
#include <iostream>
#include <string>

int main() {
    std::string name;
    std::cout << "Enter name: ";
    std::cin >> name;  // "John Doe" 입력 시 "John"만 저장
    
    std::cout << "Hello, " << name << '\n';  // Hello, John
    
    // "Doe"는 입력 버퍼에 남아있음!
    std::string remaining;
    std::cin >> remaining;
    std::cout << "Remaining: " << remaining << '\n';  // Remaining: Doe
}

해결법 1: getline 사용 (권장)

// ✅ getline으로 전체 줄 읽기
#include <iostream>
#include <string>

int main() {
    std::string name;
    std::cout << "Enter name: ";
    std::getline(std::cin, name);  // 엔터까지 전체 줄 읽기
    
    std::cout << "Hello, " << name << '\n';  // Hello, John Doe
}

해결법 2: cin과 getline 혼용 시 주의

// ❌ 문제 상황
#include <iostream>
#include <string>

int main() {
    int age;
    std::string name;
    
    std::cout << "Enter age: ";
    std::cin >> age;  // "25" 입력 후 엔터 → 엔터가 버퍼에 남음
    
    std::cout << "Enter name: ";
    std::getline(std::cin, name);  // 버퍼의 엔터를 읽어서 빈 문자열!
    
    std::cout << "Age: " << age << ", Name: " << name << '\n';
    // Age: 25, Name:  (빈 문자열)
}

// ✅ 해결: cin.ignore()로 버퍼 비우기
#include <iostream>
#include <string>
#include <limits>

int main() {
    int age;
    std::string name;
    
    std::cout << "Enter age: ";
    std::cin >> age;
    
    // 버퍼에 남은 엔터 제거
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    
    std::cout << "Enter name: ";
    std::getline(std::cin, name);
    
    std::cout << "Age: " << age << ", Name: " << name << '\n';
    // Age: 25, Name: John Doe
}

cin.ignore() 설명:

  • std::cin.ignore(n, delim): 최대 n개 문자를 무시하고, delim을 만나면 중단
  • std::numeric_limits<std::streamsize>::max(): 버퍼 전체 크기
  • '\n': 엔터까지 무시

실수 17: 벡터 크기 미확인

vector는 동적 배열이지만, operator[]는 범위 검사를 하지 않습니다. 빈 벡터에 접근하면 미정의 동작이 발생합니다.

// ❌ 크래시 코드
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec;  // 빈 벡터 (size=0, capacity=0)
    
    vec[0] = 42;  // ❌ 범위 밖 접근 (미정의 동작)
    std::cout << vec[0] << '\n';  // 쓰레기 값 또는 크래시
}

// 미정의 동작: 컴파일은 되지만 실행 시 문제 발생

해결법 1: 크기 지정 초기화

// ✅ 올바른 코드 - 크기 지정
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec(5);  // 크기 5, 모든 요소 0으로 초기화
    vec[0] = 42;  // OK
    std::cout << vec[0] << '\n';  // 42
    
    // 크기와 초기값 지정
    std::vector<int> vec2(5, 10);  // 크기 5, 모든 요소 10
    // vec2: [10, 10, 10, 10, 10]
}

해결법 2: push_back 사용

// ✅ 올바른 코드 - push_back으로 추가
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec;  // 빈 벡터
    
    vec.push_back(42);  // 자동 확장 (size=1)
    vec.push_back(10);  // size=2
    vec.push_back(20);  // size=3
    
    std::cout << vec[0] << '\n';  // 42
    std::cout << vec.size() << '\n';  // 3
}

해결법 3: at() 사용 (범위 검사)

// ✅ 안전한 접근 - at()은 범위 검사
#include <vector>
#include <iostream>
#include <stdexcept>

int main() {
    std::vector<int> vec = {1, 2, 3};
    
    try {
        std::cout << vec.at(0) << '\n';  // 1 (OK)
        std::cout << vec.at(10) << '\n';  // 예외 발생
    } catch (const std::out_of_range& e) {
        std::cerr << "오류: " << e.what() << '\n';
        // 오류: vector::_M_range_check: __n (which is 10) >= this->size() (which is 3)
    }
}

operator[] vs at() 비교:

방법범위 검사예외 발생성능사용 시기
vec[i]❌ 없음❌ 없음빠름인덱스가 확실히 유효할 때
vec.at(i)✅ 있음✅ out_of_range약간 느림안전성이 중요할 때

실전 패턴:

#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec;
    
    // ✅ 패턴 1: 크기 확인 후 접근
    if (vec.size() > 0) {
        std::cout << vec[0] << '\n';
    }
    
    // ✅ 패턴 2: empty() 확인
    if (!vec.empty()) {
        std::cout << vec[0] << '\n';
    }
    
    // ✅ 패턴 3: 범위 기반 for (안전)
    for (int x : vec) {
        std::cout << x << '\n';
    }
    
    // ✅ 패턴 4: reserve로 용량 미리 확보
    vec.reserve(100);  // 100개 요소를 위한 메모리 할당
    for (int i = 0; i < 100; ++i) {
        vec.push_back(i);  // 재할당 없이 추가
    }
}

실수 18: switch문에서 break 누락

// ❌ 버그 (의도하지 않은 fall-through)
int main() {
    int x = 1;
    
    switch (x) {
        case 1:
            std::cout << "One\n";
            // break 없음!
        case 2:
            std::cout << "Two\n";
            break;
    }
    
    // 출력: One\nTwo\n (의도하지 않음)
}

해결:

// ✅ 올바른 코드
int main() {
    int x = 1;
    
    switch (x) {
        case 1:
            std::cout << "One\n";
            break;  // 필수
        case 2:
            std::cout << "Two\n";
            break;
        default:
            std::cout << "Other\n";
            break;
    }
}

초보자를 위한 디버깅 가이드

디버깅 프로세스

1단계: 에러 메시지 읽기

에러 메시지 구조:
main.cpp:10:5: error: 'cout' was not declared in this scope
^^^^^^^  ^^  ^^ ^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
파일     줄  열 타입    에러 내용

읽는 순서:
1. 파일과 줄 번호 확인 (main.cpp:10)
2. 에러 타입 확인 (error vs warning)
3. 에러 내용 이해 ('cout' was not declared)
4. 제안 확인 (note: suggested alternative: 'std::cout')

2단계: 첫 번째 에러만 집중

main.cpp:10:5: error: 'cout' was not declared in this scope
main.cpp:10:5: note: suggested alternative: 'std::cout'
main.cpp:11:5: error: 'endl' was not declared in this scope
main.cpp:12:5: error: expected ';' before 'return'
... (중략 50줄) ...

첫 번째 에러만 고치세요!
→ #include <iostream> 추가
→ 나머지 에러도 자동으로 해결될 가능성 높음

3단계: 이진 탐색으로 에러 위치 찾기

// 500줄 코드에서 에러가 나는데 위치를 모를 때

// 1. 코드를 절반으로 나누어 주석 처리
// 상반부 주석 처리 → 컴파일
// 에러 없으면 하반부에 문제
// 에러 있으면 상반부에 문제

// 2. 문제 있는 절반을 다시 절반으로
// 반복하여 에러 위치를 좁혀감

// 예제:
int main() {
    // 코드 1-250줄
    /* 주석 처리 */
    
    // 코드 251-500줄
    // ...
}

팁 1: 컴파일러 경고를 에러로 취급

컴파일러 경고는 잠재적 버그를 알려줍니다. 경고를 무시하지 말고 에러로 취급하세요.

# GCC/Clang - 권장 플래그
g++ -std=c++17 -Wall -Wextra -Werror -pedantic main.cpp -o main

# 플래그 설명:
# -Wall: 기본 경고 활성화
# -Wextra: 추가 경고 활성화
# -Werror: 경고를 에러로 취급 (컴파일 실패)
# -pedantic: 표준 준수 엄격 검사

# MSVC
cl /W4 /WX main.cpp

# /W4: 경고 레벨 4 (최대)
# /WX: 경고를 에러로 취급

경고 예제:

// 경고가 나는 코드
#include <iostream>

int main() {
    int x = 10;
    unsigned int y = 5;
    
    if (x > y) {  // warning: comparison of integer expressions of different signedness
        std::cout << "x > y\n";
    }
    
    int arr[5];
    for (int i = 0; i <= 5; ++i) {  // warning: array subscript is above array bounds
        arr[i] = i;
    }
}

// 경고를 고친 코드
int main() {
    int x = 10;
    int y = 5;  // unsigned 제거
    
    if (x > y) {
        std::cout << "x > y\n";
    }
    
    int arr[5];
    for (int i = 0; i < 5; ++i) {  // <= 를 < 로 수정
        arr[i] = i;
    }
}

팁 2: 최소 재현 코드 (MCVE) 작성

MCVE (Minimal, Complete, Verifiable Example): 에러를 재현하는 최소한의 코드

// ❌ 복잡한 코드 (500줄)
// 어디서 에러가 나는지 모름

// ✅ 최소 재현 코드 (10줄)
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    std::cout << vec[0] << '\n';  // 에러 발생!
    return 0;
}

MCVE 작성 단계:

  1. 에러가 나는 부분만 추출
  2. 불필요한 코드 제거
  3. 필요한 헤더만 포함
  4. 독립적으로 컴파일 가능하게 작성

팁 3: 온라인 컴파일러 활용

온라인 컴파일러는 환경 설정 없이 빠르게 테스트할 수 있습니다.

추천 사이트:

  1. Compiler Explorer (godbolt.org)

    • 여러 컴파일러 비교 (GCC, Clang, MSVC)
    • 어셈블리 코드 확인
    • 최적화 레벨별 결과 비교
  2. cpp.sh

    • 간단한 테스트용
    • 빠른 실행
  3. Wandbox

    • 다양한 컴파일러 버전
    • 빠른 응답 속도
  4. OnlineGDB

    • 디버거 지원
    • 단계별 실행 가능

사용 시나리오:

  • 문법 확인 (예: C++17 기능 테스트)
  • 컴파일러별 동작 차이 확인
  • 에러 메시지 비교
  • 최적화 효과 확인

팁 4: 디버거 사용법

GDB (Linux/Mac):

# 디버그 정보 포함하여 컴파일
g++ -g main.cpp -o main

# GDB 실행
gdb ./main

# GDB 명령어
(gdb) break main        # main 함수에 중단점
(gdb) run               # 프로그램 실행
(gdb) next              # 다음 줄 (함수 호출 건너뜀)
(gdb) step              # 다음 줄 (함수 내부로 진입)
(gdb) print x           # 변수 x 값 출력
(gdb) continue          # 다음 중단점까지 실행
(gdb) quit              # 종료

Visual Studio (Windows):

1. F9: 중단점 설정/해제
2. F5: 디버깅 시작
3. F10: 다음 줄 (Step Over)
4. F11: 함수 내부로 (Step Into)
5. Shift+F11: 함수 밖으로 (Step Out)
6. 변수에 마우스 올리면 값 표시

VS Code (크로스 플랫폼):

// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "C++ Debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/main",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "preLaunchTask": "build"
        }
    ]
}

팁 5: 로깅으로 디버깅

printf 디버깅 (간단하지만 효과적):

#include <iostream>

int main() {
    int x = 10;
    std::cout << "DEBUG: x = " << x << '\n';  // 변수 값 확인
    
    std::vector<int> vec = {1, 2, 3};
    std::cout << "DEBUG: vec.size() = " << vec.size() << '\n';  // 크기 확인
    
    for (size_t i = 0; i < vec.size(); ++i) {
        std::cout << "DEBUG: vec[" << i << "] = " << vec[i] << '\n';
    }
}

조건부 디버그 출력:

#include <iostream>

// 디버그 모드 플래그
#define DEBUG 1

#if DEBUG
    #define LOG(x) std::cout << "[DEBUG] " << x << '\n'
#else
    #define LOG(x)  // 릴리스 빌드에서는 무시
#endif

int main() {
    int x = 10;
    LOG("x = " << x);  // 디버그 모드에서만 출력
    
    // 릴리스 빌드: g++ -DDEBUG=0 main.cpp
}

체크리스트

컴파일 전 체크리스트

  • 모든 클래스/구조체 정의 끝에 세미콜론이 있는가?
  • 사용하는 모든 기능의 헤더를 포함했는가?
  • std:: 접두사를 붙였는가? (또는 using 선언)
  • main 함수가 int main()인가?
  • 함수 선언과 정의의 시그니처가 일치하는가?

런타임 전 체크리스트

  • 모든 포인터를 초기화했는가?
  • 배열 인덱스가 범위 내인가?
  • new/delete 짝이 맞는가?
  • 문자열 비교를 == 로 하지 않았는가? (C 스타일)
  • 지역 변수 주소를 반환하지 않았는가?

코드 리뷰 체크리스트

  • 컴파일러 경고가 없는가? (-Wall -Wextra)
  • 스마트 포인터를 사용하는가?
  • const 참조를 적절히 사용하는가?
  • 범위 기반 for문을 사용하는가? (배열 순회)
  • switch문에 break가 있는가?

정리

실수 빈도 Top 5

  1. 세미콜론 누락 (클래스 정의 끝)
  2. 헤더 미포함 (<iostream>, <string> 등)
  3. std:: 생략 (coutstd::cout)
  4. 포인터 초기화 안 함
  5. 배열 범위 초과

핵심 규칙

  1. 클래스/구조체 정의 끝에는 세미콜론
  2. 사용하는 모든 기능의 헤더를 포함
  3. std:: 접두사를 붙이거나 using 선언
  4. 포인터는 반드시 초기화 (nullptr 또는 유효한 주소)
  5. 배열 인덱스는 0부터 size-1까지
  6. new/delete는 짝으로 (또는 스마트 포인터 사용)
  7. 컴파일러 경고를 무시하지 마세요 (-Wall -Wextra)

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ 개요 | “처음 배우는” C++ 완벽 가이드
  • C++ 포인터 | “어렵다는 포인터” 5분 만에 이해하기
  • C++ 개발 환경 설정 | Visual Studio·GCC·Clang 설치
  • C++ LNK2019 | “unresolved external symbol” 링커 에러 해결

자주 묻는 질문 (FAQ)

Q. C와 C++의 차이는 뭔가요?

A. C++는 C의 상위 집합이지만, 몇 가지 차이가 있습니다:

  • C++는 <iostream>, C는 <stdio.h>
  • C++는 std::string, C는 char[]
  • C++는 new/delete, C는 malloc/free
  • C++는 클래스·템플릿·예외 처리 지원

Q. void main()을 쓰면 안 되나요?

A. 표준이 아닙니다. 일부 컴파일러는 허용하지만, 이식성이 없습니다. 항상 int main()을 사용하세요.

Q. using namespace std를 쓰면 안 되나요?

A. 헤더 파일에는 절대 쓰지 마세요 (이름 충돌). .cpp 파일에서는 괜찮지만, std:: 접두사를 쓰는 것이 더 명확합니다.

Q. 포인터와 참조의 차이는 뭔가요?

A.

  • 포인터: 주소를 저장, nullptr 가능, 재할당 가능
  • 참조: 별칭, nullptr 불가, 재할당 불가

초보자는 참조를 먼저 익히는 것을 권장합니다.


마치며

C++ 초보자가 겪는 대부분의 에러는 패턴이 있습니다. 이 글에서 다룬 15가지 실수를 숙지하면, 에러 메시지를 보고 5분 안에 해결할 수 있게 됩니다.

학습 로드맵:

  1. 이 글의 실수들을 모두 경험해 보세요 (직접 에러를 만들고 고치기)
  2. 작은 프로그램을 많이 작성하세요 (계산기, 숫자 맞추기 게임 등)
  3. 컴파일러 경고를 읽는 습관을 들이세요
  4. 스마트 포인터와 STL을 익히세요

다음 단계: 기본 실수를 극복했다면, C++ 포인터 완벽 가이드C++ STL vector 가이드를 읽어보세요.


관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |