CppCommon Pitfalls

Common Pitfalls

Certain mistakes show up again and again in C++ code, from beginner scripts to production codebases. Most of them compile without complaint and only misbehave at runtime — sometimes only occasionally, which makes them especially frustrating to track down. This page is a quick reference for what to watch for.

Pitfall

Why it bites

Uninitialized variables

A local variable with no initializer holds whatever garbage was already in that memory — not zero. Reading it is undefined behavior, and it can even appear to "work" for a while before failing.

Dangling pointers/references

A pointer or reference to an object that has already been destroyed (e.g. a local variable after the function returned, or memory already deleted). Using it is undefined behavior even though the pointer still holds an address.

Off-by-one errors

Looping with <= instead of < against a size, or forgetting that valid indices run from 0 to size - 1, reads or writes one element past the end of an array/vector.

Mixing signed and unsigned comparisons

Comparing a signed int to an unsigned size_t implicitly converts the signed value to unsigned. A negative number becomes a huge positive one, silently breaking loop conditions like i >= 0 when i is unsigned.

Integer division truncation

7 / 2 evaluates to 3, not 3.5, because both operands are integers. The fractional part is discarded, not rounded — cast at least one operand to double if you need a real division.

Shallow copy of resource-owning classes

Without a proper copy constructor, the compiler-generated one copies a raw pointer member as-is. Both copies then point at the same heap memory, and destroying one leaves the other holding a dangling pointer (and a double-free waiting to happen).

Missing virtual destructor in a polymorphic base

delete basePtr; where basePtr actually points to a derived object only runs the base class destructor if the destructor isn’t virtual — the derived part is never cleaned up, leaking any resources it owned.

Memory leaks from mismatched new/delete

Every new needs exactly one matching delete (and new[] needs delete[], not delete). An early return, a thrown exception, or a forgotten pointer skips the delete and the memory is never reclaimed for the life of the program.

Using = instead of ==

if (x = 5) assigns 5 to x and then evaluates the (always truthy) result, instead of comparing. It compiles cleanly and silently does the wrong thing — enable compiler warnings so this gets flagged.

Array-to-pointer decay in function parameters

Passing a C array to a function that takes int arr[] (or equivalently int* arr) loses all size information inside that function — sizeof(arr) there returns the size of a pointer, not the array, a frequent source of subtle bugs.

a-few-of-these-in-action.cpp

CPP
#include <cstddef>
#include <iostream>

void printSize(int arr[]) {
  // arr has decayed to a pointer here — this is sizeof(int*), not the array size!
  std::cout << "sizeof inside function: " << sizeof(arr) << "\n";
}

int main() {
  int numbers[5] = {1, 2, 3, 4, 5};
  std::cout << "sizeof in main: " << sizeof(numbers) << "\n"; // 20 bytes
  printSize(numbers);                                          // 8 bytes (a pointer)

  int x;
  if (x = 5) { // bug: assignment, not comparison — also uses uninitialized x
    std::cout << "This always runs, x is now 5\n";
  }

  std::size_t count = 3;
  int i = -1;
  if (i < count) { // i converts to a huge unsigned number — condition is false!
    std::cout << "unreachable on most compilers\n";
  }

  return 0;
}