C++ 초보자가 자주 하는 실수 Top 15 | 컴파일 에러부터 런타임 크래시까지
이 글의 핵심
C++ 초보자가 자주 하는 실수 Top 15에 대한 실전 가이드입니다. 컴파일 에러부터 런타임 크래시까지 등을 예제와 함께 상세히 설명합니다.
들어가며: “C++ 시작했는데 에러만 100개…"
"Hello World도 컴파일이 안 돼요”
C++를 처음 배우는 사람들이 반드시 겪는 실수들이 있습니다. 세미콜론 하나, 헤더 파일 하나 빠뜨려도 수십 줄의 에러 메시지가 쏟아집니다. 이 글은 C++ 입문자가 가장 자주 하는 실수 15가지를 정리하고, 각각의 에러 메시지와 해결법을 알려드립니다.
이 글을 읽으면:
- 컴파일 에러 메시지를 읽고 5분 안에 해결할 수 있습니다
- 같은 실수를 반복하지 않게 됩니다
- C++ 문법의 핵심 규칙을 이해하게 됩니다
목차
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
읽는 순서:
- 파일:줄:
main.cpp:10- 어느 파일의 몇 번째 줄 - 에러 타입:
error(컴파일 실패) vswarning(경고) - 에러 내용:
'cout' was not declared- 무엇이 문제인지 - 제안:
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 작성 단계:
- 에러가 나는 부분만 추출
- 불필요한 코드 제거
- 필요한 헤더만 포함
- 독립적으로 컴파일 가능하게 작성
팁 3: 온라인 컴파일러 활용
온라인 컴파일러는 환경 설정 없이 빠르게 테스트할 수 있습니다.
추천 사이트:
-
Compiler Explorer (godbolt.org)
- 여러 컴파일러 비교 (GCC, Clang, MSVC)
- 어셈블리 코드 확인
- 최적화 레벨별 결과 비교
-
- 간단한 테스트용
- 빠른 실행
-
- 다양한 컴파일러 버전
- 빠른 응답 속도
-
- 디버거 지원
- 단계별 실행 가능
사용 시나리오:
- 문법 확인 (예: 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
- 세미콜론 누락 (클래스 정의 끝)
- 헤더 미포함 (
<iostream>,<string>등) - std:: 생략 (
cout→std::cout) - 포인터 초기화 안 함
- 배열 범위 초과
핵심 규칙
- 클래스/구조체 정의 끝에는 세미콜론
- 사용하는 모든 기능의 헤더를 포함
- std:: 접두사를 붙이거나 using 선언
- 포인터는 반드시 초기화 (
nullptr또는 유효한 주소) - 배열 인덱스는 0부터 size-1까지
- new/delete는 짝으로 (또는 스마트 포인터 사용)
- 컴파일러 경고를 무시하지 마세요 (-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분 안에 해결할 수 있게 됩니다.
학습 로드맵:
- 이 글의 실수들을 모두 경험해 보세요 (직접 에러를 만들고 고치기)
- 작은 프로그램을 많이 작성하세요 (계산기, 숫자 맞추기 게임 등)
- 컴파일러 경고를 읽는 습관을 들이세요
- 스마트 포인터와 STL을 익히세요
다음 단계: 기본 실수를 극복했다면, C++ 포인터 완벽 가이드와 C++ STL vector 가이드를 읽어보세요.
관련 글
- C++ 시리즈 전체 보기
- C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
- C++ ADL |
- C++ Aggregate Initialization |