C++ PROGRAMMING · 13 MIN STUDY

Compiler warnings, debugging, and testing

Read compiler diagnostics, use breakpoints, inspect program state, and test edge cases.

Learning objectives

  • Distinguish syntax/compile, link, runtime, and logic problems.
  • Read a compiler diagnostic and understand what a warning does—and does not—promise.
  • Build with debug information and use breakpoints, stepping, and variable inspection.
  • Design normal, boundary, empty, and invalid-input tests.

Prerequisites: compile and run a basic C++ program, read variables and loops, and understand compiler diagnostics. Review Your first C++ program, Compiler toolchain, and Loops.

Classify the problem first

Problem typeWhen it appearsExample
Syntax / compile errorDuring translationMissing semicolon or invalid expression
Link errorDuring linkingA declared function has no matching definition
Runtime failureWhile the program executesOpening a file fails and the program reports it
Logic errorProgram runs but gives the wrong answerUsing < where the algorithm requires <=

Do not treat every invalid memory access as a predictable “runtime error”: many such defects are undefined behavior, and the language does not guarantee a crash or a diagnostic. Sanitizers and debuggers can help expose some defects, but they do not prove a program correct.

Read diagnostics and enable warnings

Start with the file name, line/column location, and first useful diagnostic. Later messages may be consequences of the first error. Fix one cause and rebuild. A warning is not necessarily a compilation failure; it is a diagnostic about code that may be risky or suspicious. Warning sets differ between compilers and versions, and a clean warning run is not proof that the program is bug-free.

g++ -std=c++17 -Wall -Wextra -Wpedantic main.cpp -o app

For Clang, use the corresponding clang++ command with the same standard and warning options. Treat -Wall as a named group of warnings, not as “every warning that exists”. During learning, avoid adding -Werror until you understand the diagnostics; it turns enabled warnings into build failures.

Use a debugger systematically

  1. Compile a debug build with symbols and without aggressive optimization, for example g++ -std=c++17 -g -O0 -Wall -Wextra -Wpedantic main.cpp -o app.
  2. Start GDB with gdb ./app.
  3. Set a breakpoint with break main or break file.cpp:line, then enter run.
  4. Use next to execute a source line without stepping into a called function, or step to enter it.
  5. Inspect state with print variable; use backtrace to inspect the call stack.
  6. Use continue to resume and quit to leave GDB.

A debugger lets you observe a particular execution. Form a hypothesis first (“the index becomes 5 before the condition is checked”), pause near that point, and inspect the relevant state. A debugger cannot by itself establish correctness for every possible input.

Test a contract, not just one example

This function returns the index of the first matching value, or -1 if no match exists. Its loop condition keeps every vector access within the valid index range.

Complete C++17 program — test normal and boundary cases
#include <cstddef>
#include <iostream>
#include <vector>

int find_index(const std::vector<int>& values, int target) {
    for (std::size_t i{0}; i < values.size(); ++i) {
        if (values[i] == target) {
            return static_cast<int>(i);
        }
    }
    return -1;
}

int main() {
    const std::vector<int> values{4, 8, 12};

    std::cout << find_index(values, 4) << '\n';
    std::cout << find_index(values, 12) << '\n';
    std::cout << find_index(values, 7) << '\n';
    std::cout << find_index({}, 7) << '\n';
}
Expected output 0 2 -1 -1

The cast is safe for this small teaching example because the vector has only three elements. In a general API, choose a return type and sentinel/optional result that can represent the full index range without narrowing.

Test categoryExample for this functionWhat it checks
First position{4, 8, 12}, target 4Index 0 is included
Last positionTarget 12Last valid index is included
Absent valueTarget 7Search ends and returns -1
Empty input{}No element is accessed

Common debugging mistakes

  • Changing many lines at once, so the cause of a new failure is unclear.
  • Ignoring the first diagnostic and chasing secondary messages.
  • Testing only the happy path or only one data value.
  • Assuming a warning-free build or successful compilation proves correctness.
  • Using print statements without a specific question about the program state.

Practice

  1. What is the difference between a compile error and a link error?
  2. Which GDB command pauses execution at the start of main?
  3. Why should a search test include an empty vector?
  4. Does -Wall enable every warning a compiler can issue?
  5. Why is a logic error different from a runtime failure?

Worked answers

Show reasoned answers
  1. A compile error prevents translation of a source unit; a link error occurs when the linker cannot resolve required symbols/definitions.
  2. break main sets a breakpoint; run starts execution under GDB.
  3. It checks that the function returns the documented “not found” result without accessing an element.
  4. No. -Wall enables a documented group of warnings; other warnings may need separate options.
  5. A runtime failure occurs during execution; a logic error is incorrect reasoning that can still produce a normal-looking execution with the wrong result.

Summary

Classify the failure, read the first useful diagnostic, build with suitable warnings and debug symbols, and investigate with a focused hypothesis. Test boundaries and invalid states as well as typical inputs; passing tests are evidence, not a proof of correctness.

References