15 Common C++ Beginner Mistakes: From Compile Errors to
이 글의 핵심
Fifteen common C++ beginner mistakes with compiler output, root cause, and fix for each: missing semicolons, forgotten headers, void main, uninitialized pointers, off-by-one loops, = vs ==, and more.
Introduction
Learning C++ means encountering a set of errors that every beginner hits in roughly the same order. The compiler’s error messages can look overwhelming — a single misplaced character can trigger five follow-on errors. But each mistake has a predictable pattern, and once you recognize the pattern, the fix takes ten seconds.
This guide covers fifteen common beginner mistakes with the actual compiler output, the root cause, and the exact fix.
Key rule: always fix the first error in the compiler output. Later errors are often caused by the first one, so fixing them one by one is slower than addressing the root.
Part 1: Compile-Time Mistakes
1. Missing Semicolon After Class/Struct Definition
// WRONG
class Player {
int health;
std::string name;
} // ← missing semicolon
int main() {
Player p; // error cascades here
}
Compiler output (GCC):
error: expected ';' after class definition
error: 'Player' was not declared in this scope
The second error is a cascade from the first. Fix the semicolon, both errors disappear.
// CORRECT
class Player {
int health;
std::string name;
}; // ← semicolon required after class/struct/union
Why: The C++ grammar requires a semicolon after the closing brace of a class definition because you can declare variables after the brace: class Player { ... } p1, p2;. The semicolon ends the declaration statement.
2. Missing #include
// WRONG
int main() {
std::cout << "Hello\n"; // cout not declared
std::vector<int> v; // vector not declared
std::sort(v.begin(), v.end()); // sort not declared
}
Compiler output:
error: 'cout' is not a member of 'std'
error: 'vector' is not declared in this scope
// CORRECT
#include <iostream> // std::cout, std::cin, std::cerr
#include <string> // std::string
#include <vector> // std::vector
#include <algorithm> // std::sort, std::find, std::max
#include <memory> // std::unique_ptr, std::shared_ptr
#include <cmath> // std::sqrt, std::pow, std::abs
int main() {
std::cout << "Hello\n";
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end());
}
Rule: include the header for every standard name you use. If the compiler says something is “not declared in this scope,” the first question is “do I have the right include?“
3. Missing std:: (Namespace Prefix)
// WRONG
#include <iostream>
int main() {
cout << "Hello\n"; // error: 'cout' was not declared
string name = "Alice"; // error: 'string' was not declared
}
// CORRECT — explicit prefix (recommended)
#include <iostream>
#include <string>
int main() {
std::cout << "Hello\n";
std::string name = "Alice";
}
// ALSO CORRECT — using declaration in .cpp files (not headers)
#include <iostream>
using std::cout;
using std::string;
int main() {
cout << "Hello\n";
string name = "Alice";
}
Avoid: using namespace std; in header files. It pollutes every file that includes yours. In .cpp files it’s acceptable but verbose using declarations for specific names are safer.
4. void main Instead of int main
// WRONG — not standard C++
void main() {
std::cout << "Hello\n";
}
Compiler output (Clang):
error: 'main' must return 'int'
// CORRECT
int main() {
std::cout << "Hello\n";
return 0; // 0 = success; can be omitted in main (implicit in C++11+)
}
// With command-line arguments:
int main(int argc, char* argv[]) {
// argc = argument count, argv = argument strings
return 0;
}
5. Using Variables Before Declaration
// WRONG
int main() {
total = 0; // error: 'total' was not declared
int total;
total = 100;
}
// CORRECT: declare before use, initialize at declaration
int main() {
int total = 0;
total = 100;
}
In C++, variables must be declared before they are used. Declare them as close to their first use as possible — this makes code easier to read and reduces the scope of each variable.
6. Const Reference for Read-Only Parameters
// WRONG: takes string by value — copies every time
void printName(std::string name) {
std::cout << name << '\n';
}
printName("Alice"); // copies "Alice" into a new std::string
printName(user.displayName); // copies the entire string
// CORRECT: const reference — no copy
void printName(const std::string& name) {
std::cout << name << '\n';
}
printName("Alice"); // binds to the literal directly
printName(user.displayName); // binds to the existing string
String-by-value vs string-by-const-reference is not just style — for long strings in hot loops, the difference is measurable. The rule: if a function only reads a string (doesn’t modify or store it), use const std::string&.
7. Forgetting to Initialize Variables
// WRONG: uninitialized variable — undefined behavior
int main() {
int count;
if (count > 0) // count has garbage value — behavior undefined
std::cout << "positive\n";
}
// CORRECT: always initialize
int count = 0;
// or
int count{}; // value-initializes to 0 for fundamental types
Uninitialized variables have indeterminate values. The program may print different results, crash, or appear to work on debug builds but fail on release builds. Always initialize.
8. Declaration/Definition Mismatch
// header.h
int computeSum(int a, int b); // declared as returning int
// implementation.cpp
double computeSum(int a, int b) { // WRONG: different return type
return a + b;
}
Compiler output:
error: conflicting declaration 'double computeSum(int, int)'
note: previous declaration as 'int computeSum(int, int)'
The header and implementation must match exactly: return type, parameter types, const qualifiers, and namespace.
Part 2: Runtime Mistakes
9. Uninitialized Pointers
// WRONG: pointer is garbage — crash on dereference
int* ptr;
*ptr = 42; // crash: writing to random memory address
std::cout << *ptr; // crash: reading garbage address
// CORRECT: always initialize pointers
int* ptr = nullptr; // explicitly null — safe to check
// Only dereference after confirming non-null
if (ptr != nullptr) {
*ptr = 42;
}
// Better: use smart pointers
auto smartPtr = std::make_unique<int>(42);
std::cout << *smartPtr << '\n'; // no manual null check needed
// Automatically freed when smartPtr goes out of scope
Golden rules for raw pointers:
- Initialize to
nullptrif not yet pointing to valid memory - Always check
if (ptr != nullptr)before dereferencing - Prefer
std::unique_ptrorstd::shared_ptrfor ownership
10. Off-by-One Loop Errors
// WRONG: goes one past the end — undefined behavior
int arr[] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; ++i) // ← should be < 5
std::cout << arr[i]; // arr[5] is out of bounds!
// CORRECT: index 0 to size-1
int arr[] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; ++i)
std::cout << arr[i];
// Even better: range-for (no index, no off-by-one possible)
for (int x : arr)
std::cout << x;
// For std::vector: use .size() and < (not <=)
std::vector<int> v = {1, 2, 3, 4, 5};
for (size_t i = 0; i < v.size(); ++i)
std::cout << v[i];
// Or safe access with bounds check:
v.at(4); // throws std::out_of_range if index invalid
Array indices in C++ run from 0 to size-1. Accessing index size is undefined behavior — the program may crash, produce wrong results, or appear to work and crash later.
11. Comparing C-Style Strings with ==
// WRONG: compares pointer addresses, not contents
char s1[] = "hello";
char s2[] = "hello";
if (s1 == s2) { // always false! comparing addresses
std::cout << "equal\n";
}
// CORRECT option 1: use strcmp
#include <cstring>
if (strcmp(s1, s2) == 0) {
std::cout << "equal\n";
}
// CORRECT option 2: use std::string (recommended)
std::string str1 = "hello";
std::string str2 = "hello";
if (str1 == str2) { // compares contents — correct
std::cout << "equal\n";
}
C-style character arrays are just pointers. The == operator compares pointer addresses, not the characters. Use std::string to avoid this class of errors entirely.
12. Returning Address of a Local Variable
// WRONG: local variable destroyed when function returns — dangling pointer
int* getNumber() {
int x = 42;
return &x; // x is destroyed at function end!
}
int* ptr = getNumber();
std::cout << *ptr; // undefined behavior — reads destroyed memory
// CORRECT option 1: return by value (most common)
int getNumber() {
return 42; // copy of value returned safely
}
// CORRECT option 2: return heap-allocated via smart pointer
std::unique_ptr<int> getNumber() {
return std::make_unique<int>(42);
}
// CORRECT option 3: output parameter (use when avoiding allocation)
void getNumber(int& result) {
result = 42;
}
Part 3: Logic Mistakes
13. Assignment = Instead of Comparison ==
// WRONG: assigns 0 to x (condition is always false)
int x = 5;
if (x = 0) { // assigns 0 to x, then tests if 0 is truthy
std::cout << "zero\n"; // never reached
}
std::cout << x; // prints 0, not 5!
// CORRECT: == for comparison
if (x == 0) {
std::cout << "zero\n";
}
Trick: “Yoda conditions” — put the constant on the left. If you accidentally use =, it’s a compile error (can’t assign to a constant):
if (0 == x) { ... } // Yoda: "if zero equals x"
if (nullptr == ptr) { ... }
Many linters and compilers warn about assignment in conditions. Enable warnings with -Wall -Wextra.
14. Integer Division Truncation
// WRONG: integer division — result is always 0 or 1
int total = 7;
int count = 2;
double average = total / count; // 7/2 = 3, not 3.5
std::cout << average; // prints 3, not 3.5
// CORRECT: cast to double before dividing
double average = static_cast<double>(total) / count;
// or
double average = (double)total / count; // C-style cast, same effect
// or
double average = 1.0 * total / count; // multiply by 1.0 to promote
std::cout << average; // 3.5
When both operands of / are integers, C++ performs integer division (truncates toward zero). To get floating-point division, at least one operand must be a floating-point type.
15. Unsigned Integer Underflow
// WRONG: unsigned can't be negative — wraps to huge number
size_t size = 0; // size_t is unsigned
size_t result = size - 1; // wraps to 18446744073709551615 on 64-bit!
// Common hidden version with .size():
std::vector<int> v = {1, 2, 3};
for (size_t i = 0; i < v.size() - 1; ++i) // WRONG if v is empty: 0-1 wraps
process(v[i], v[i + 1]);
// CORRECT: check before subtracting from unsigned
if (!v.empty()) {
for (size_t i = 0; i < v.size() - 1; ++i)
process(v[i], v[i + 1]);
}
// Or use signed integer for the loop
for (int i = 0; i < static_cast<int>(v.size()) - 1; ++i)
process(v[i], v[i + 1]);
// Or use iterators (no index arithmetic)
for (auto it = v.begin(); it + 1 != v.end(); ++it)
process(*it, *(it + 1));
Reading Compiler Errors
When you get a long list of errors:
- Look at the file name and line number in the first error —
main.cpp:15:3: - Read the error type:
error:is a problem,warning:is a potential problem,note:is context - Fix the first error only — compile again and let the rest disappear
- Enable all warnings: compile with
-Wall -Wextra(GCC/Clang) — catches common mistakes early
# Compile with full warnings
g++ -Wall -Wextra -Wpedantic -std=c++17 main.cpp -o main
# Or with Clang
clang++ -Wall -Wextra -std=c++17 main.cpp -o main
Debugging Habits
# Use a small reproducer — comment out code until the error disappears
# Then you know what caused it
# For runtime crashes: use AddressSanitizer
g++ -fsanitize=address -g main.cpp -o main && ./main
# Reports buffer overflows, use-after-free, and more with exact line numbers
# Online compilers for quick tests
# godbolt.org — multiple compilers, instant feedback
# wandbox.org — run code and see output
Top Five to Memorize
};— class/struct definitions need a semicolon after the closing brace#include— each standard name needs its header; error “not declared” means a missing includestd::— prefix standard names, or useusing std::cout;declarations in.cppfiles- Initialize pointers — always
int* ptr = nullptr;before use; check before dereferencing < sizenot<= size— array/vector indices are 0 to size-1; use range-for to avoid indexing entirely
Key Takeaways
- Fix the first error first — one missing
;can cascade into five errors; fix the root, not the symptoms - Every standard name needs its header —
coutneeds<iostream>,vectorneeds<vector>,sortneeds<algorithm> int main(), notvoid main()— the return type is mandated by the standard- Initialize all variables — uninitialized values cause undefined behavior that appears random
==for comparison,=for assignment — the compiler often warns aboutif (x = 0)with-Wall- Integer division truncates — cast to
doublewhen you need decimal results -Wall -Wextra— enabling warnings catches most of these mistakes before they become bugs
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Fix missing semicolons after classes, forgotten headers, void main, pointer bugs, off-by-one loops, = vs ==, and how to … 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [C++ Segmentation Fault: Five Causes and Debugging with GDB,](/en/blog/cpp-error-02-segmentation-fault/
- C++이란? 역사, 현황, 그리고 시작 전에 알아둘 것 | C++ 입문 가이드
- C++ 포인터 | ‘어렵다는 포인터’ 5분 만에 이해하기 [그림으로 설명]
- C++ 개발 환경 구축 | ‘C++ 어디서 시작하죠?” 컴파일러 설치부터 Hello World까지
이 글에서 다루는 키워드 (관련 검색어)
C++, Beginner, Compile errors, Debugging, Learning, Common mistakes 등으로 검색하시면 이 글이 도움이 됩니다.