SovranCode
Modern C++ Lesson 44
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ Unit Testing: Normal and Boundary Cases
NEXT LESSONC++ Performance: Benchmark Before Optimizing
C++ foundations · Lesson 44 About 50 min with practice

C++ Sanitizers: Find Memory and Undefined Behavior Bugs

Learn C++ AddressSanitizer, UndefinedBehaviorSanitizer and static analysis with a clearly isolated local diagnostic fixture, safe repairs and honest limits on what a clean run proves.

Before you begin

Know vectors, bounds, pointers, lifetime and compiler commands. Unsafe diagnostic code is local-only and is not offered as a normal in-page runnable example.

What you will learn

  • Distinguish compiler warnings, static analysis and runtime instrumentation.
  • Recognize the cause behind a bounds diagnostic.
  • Build with supported sanitizer flags locally.
  • Repair bounds, lifetime and arithmetic issues rather than suppressing reports.
  • Explain why clean diagnostics are useful evidence but not proof.

Different checks look for different kinds of mistakes

A program can compile and still use memory incorrectly. Compiler warnings can highlight suspicious source. Static analysis examines possible paths without running the whole program in the ordinary way. A sanitizer adds checks to an instrumented build so certain mistakes can be detected while that build runs. These tools complement tests; they do not replace a clear contract or correct ownership design.

AddressSanitizer, often called ASan, can detect many invalid memory accesses such as reading beyond allocated storage or using freed memory. UndefinedBehaviorSanitizer, often called UBSan, checks selected categories such as signed integer overflow. Their exact support depends on the compiler, platform and enabled flags. Installing a source editor does not install a sanitizer runtime, and a language-standard flag does not enable these checks by itself.

This lesson keeps a deliberately invalid memory-access fixture separate from the safe examples. It is for a local instrumented investigation, not an ordinary Run-button exercise. We do not assign a predictable normal output to undefined behavior. If your toolchain cannot run the diagnostic setup, retain the safe examples and notes rather than executing the bad access without instrumentation and treating a lucky-looking output as a result.

An owner stores three values at zero-based indices zero, one and two. An attempted access at index three crosses the valid boundary. A separate repaired path checks the index first and safely selects thirty at index two.
A sanitizer can expose an invalid access in an exercised path. The repair is to establish valid bounds or lifetime before access, not to memorize a diagnostic address or assume a clean run proves the program safe.
C++ PRACTICE RUNNER

Check the signed index before indexing

The safe function returns a value copy or no value. It never reads the missing element.

#include <iostream>
#include <optional>
#include <vector>
std::optional<int> checkedAt(const std::vector<int>& values, int index) {
    if (index < 0 || static_cast<std::size_t>(index) >= values.size())
        return std::nullopt;
    return values[static_cast<std::size_t>(index)];
}
int main() {
    const std::vector<int> values{10, 20, 30};
    const auto result = checkedAt(values, 3);
    if (result) std::cout << *result << '\n';
    else std::cout << "No element\n";
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
No element

After editing inputs, calculate a new prediction; this reference describes only the starter.

Three elements have indices zero through two

The vector contains three elements, so size is three. The first index is zero, and the last valid index is two. An index equal to size is outside the element range. checkedAt rejects negative indices before converting to the unsigned size type, then rejects indices greater than or equal to size. Only after those checks does it use the subscript operator. Follow that order carefully; converting a negative number first can make it look like a very large positive index.

When a tool reports an invalid read, start with the source operation and the relevant object's size or lifetime. The reported machine address helps a diagnostic tool locate memory, but it is not a stable expected value for a lesson. Reports can differ by platform, compiler version and optimization. Focus on the access category, call site, allocation or lifetime information, and the smallest input that reproduces the error.

A common mistake is to change <= to < in a loop without checking what the loop is meant to do. That may repair a particular overrun, but the real proof is that every access uses an index in the valid element range. Test empty data, index zero, the last element, index equal to size and a negative index. These cases make the rule visible instead of relying on an unexplained change that merely quiets one report.

  • The owner has size 3 and valid indices 0, 1 and 2.
  • A requested index of 3 equals size and must be rejected.
  • The repaired function returns nullopt before any subscript access.
  • The caller prints No element without reading outside storage.
SANITIZER INVESTIGATION · LOCAL ONLY

Local-only ASan investigation: one invalid read

bad-index.cpp

Deliberately unsafe fixture. Build and run only with the instrumented command below in a disposable local practice directory. No ordinary stdout transcript is promised; inspect the sanitizer's error category and nonzero status. This fixture is not sent to the in-page compiler runner.

#include <iostream>
#include <vector>
int main(int argc, char**) {
    const std::vector<int> values{10, 20, 30};
    const auto index = static_cast<std::size_t>(argc + 2);
    std::cout << values[index] << '\n'; // Invalid when launched with no extra arguments.
}
LOCAL TERMINAL COMMANDS

Instrument the isolated fixture with supported Clang tools

These commands are for a local Clang environment with its sanitizer runtime. Run the fixture with no extra arguments. A detected error should produce a diagnostic and a nonzero exit, not a trusted number. Do not use the fixture as application code.

clang++ -std=c++20 -O1 -g -fsanitize=address -fno-omit-frame-pointer bad-index.cpp -o bad-index
./bad-index

Build the repaired main.cpp separately:
clang++ -std=c++20 -O1 -g -fsanitize=address,undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer main.cpp -o checked
./checked

Optional supported Clang static-analysis pass:
clang++ --analyze -std=c++20 main.cpp

Run the checked build, not an old executable

Compile and link with the sanitizer flags, then run the explicitly named instrumented executable. Adding a flag only to an unrelated editor setting does not instrument an existing binary. Read the build result before launching, because a failed rebuild can leave an older executable on disk. The command uses debug information and a frame-pointer option to help reports identify call paths. These options aid diagnosis, not correctness by themselves.

AddressSanitizer changes memory use and runtime behavior. Its performance is not a production benchmark. Our ordinary lesson runner has its own resource limits and does not silently add these diagnostic options, so the local-only fixture is kept outside it. The separately supplied safe programs can still run through the normal compiler interface when that sandbox is available. Keep the environment and build flags in your investigation notes.

Do not suppress a report merely to make the terminal look clean. Reduce the failing case, identify the invalid access and repair the ownership, lifetime or bounds rule. Rebuild and run the instrumented repaired program, then run its functional tests too. A suppression can be appropriate for a carefully understood external limitation, but it is not an explanation for a real defect in code you control.

C++ PRACTICE RUNNER

Keep the string owner alive before using a view

Returning an owning string repairs the would-be dangling local return. No invalid version is executed here.

#include <iostream>
#include <string>
#include <string_view>
std::string makeLabel() { return "ready"; }
int main() {
    const auto owner = makeLabel();
    const std::string_view view{owner};
    std::cout << view << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
ready

After editing inputs, calculate a new prediction; this reference describes only the starter.

C++ PRACTICE RUNNER

Check before an addition can overflow

The maximum int cannot be incremented safely. The guard runs before the addition, not afterward.

#include <iostream>
#include <limits>
#include <optional>
std::optional<int> increment(int value) {
    if (value == std::numeric_limits<int>::max()) return std::nullopt;
    return value + 1;
}
int main() {
    const auto result = increment(std::numeric_limits<int>::max());
    if (result) std::cout << *result << '\n';
    else std::cout << "Cannot increment\n";
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
Cannot increment

After editing inputs, calculate a new prediction; this reference describes only the starter.

A clean run only covers the paths that ran

Sanitizers do not detect every bug. A path that was never executed might still contain an invalid access. Some logical mistakes, such as choosing the wrong valid element, do not violate memory rules at all. UBSan's enabled checks are selected categories, not a universal definition of correctness. State which build and inputs you tested, and do not turn the absence of a report into a promise that every future execution is safe.

Static analysis can explore possible issues without the same concrete runtime input, but it also has limits and can require interpretation. Compiler warnings, static-analysis findings, runtime diagnostics and failed behavior tests are different evidence. Investigate them according to their cause. A static warning is not automatically a runtime crash, and a program that happens to return zero is not automatically free of memory errors.

The safe repairs use the same principles from earlier lessons: keep an owner alive, check the index, and guard arithmetic before the risky operation. Do not teach tools as magic that makes raw invalid access acceptable. Use them as feedback while improving a design whose rules you can explain in plain words. Keep the smallest failing input and the repaired test as a record so the same mistake is less likely to return.

Build a safe checked lookup and verify it with tools

Read count zero through five, that many readings from minus one hundred through one hundred, and a whole signed zero-based index. Invalid required input prints Invalid input with status one. A well-formed index outside the data is a valid missing lookup: print No element with status zero. A match prints Value, including a real zero. Validate bounds before unsigned conversion and subscript access.

Use the safe lab source as main.cpp in a supported local sanitizer build. Test empty data, first and last elements, negative indices, index equal to size and the largest supported int index. Record the compiler, flags, observed status and any diagnostic category. The normal reference output describes the safe starter, not the unsafe fixture. If diagnostic tools are unavailable, say so in your notes; do not invent a clean sanitizer result.

LOCAL TERMINAL COMMANDS

Build one complete safe example

Choose an installed compiler. These commands run locally, not in the source editor. The in-page Run button requires the configured compiler sandbox. Diagnostic fixtures and multi-file projects use their separate instructions.

macOS / Linux:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or use g++ with the same options.
./lesson

Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exe
APPLY THE CONCEPT

Independent lab

Use safe zero-based lookup, then run the safe program under a supported local diagnostic build.

Acceptance checklist

  • Read count 0..5, bounded readings -100..100 and a whole signed zero-based index.
  • Reject malformed, missing or out-of-range required data as Invalid input with status 1.
  • Check a negative index before unsigned conversion and reject index >= size before access.
  • Print Value for a found copy or No element for a valid missing request, both with status 0.
  • Run the safe source with supported sanitizer flags and boundary cases; record actual evidence and do not run the unsafe fixture uninstrumented.
Need a hint?
  1. Zero-based means the last valid index is size minus one, not size.
  2. A missing optional is different from a contained zero reading.
  3. No host-code fallback or sanitizer availability should be assumed from an editor's Run button.
C++ PRACTICE RUNNER

Write and run your lab

Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Your lab evidence and notes

Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.

Loading local notes…

Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.

Reveal the explained lab solution
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
#include <optional>
#include <vector>
std::optional<int> checkedAt(const std::vector<int>& values, int index) {
    if (index < 0 || static_cast<std::size_t>(index) >= values.size())
        return std::nullopt;
    return values[static_cast<std::size_t>(index)];
}
int main() {
    int count{};
    if (!readInteger(count) || count < 0 || count > 5) {
        std::cout << "Invalid input\n"; return 1;
    }
    std::vector<int> values;
    for (int i = 0; i < count; ++i) {
        int value{};
        if (!readInteger(value) || value < -100 || value > 100) {
            std::cout << "Invalid input\n"; return 1;
        }
        values.push_back(value);
    }
    int index{};
    if (!readInteger(index)) { std::cout << "Invalid input\n"; return 1; }
    const auto result = checkedAt(values, index);
    if (result) std::cout << "Value: " << *result << '\n';
    else std::cout << "No element\n";
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Expected output

Value: 30

The index is zero-based and checked while still signed. Only a proven valid index is converted and used to access storage.

The result is an owned integer copy or an empty optional, so no pointer into a destroyed container escapes.

Functional tests and an instrumented clean run answer different questions. Record actual tool availability and never claim an unperformed sanitizer check passed.

Review before moving on

Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.

  • Distinguish compiler warnings, static analysis and runtime instrumentation.
  • Recognize the cause behind a bounds diagnostic.
  • Build with supported sanitizer flags locally.
  • Repair bounds, lifetime and arithmetic issues rather than suppressing reports.
  • Explain why clean diagnostics are useful evidence but not proof.

References and further reading

Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.

  • Clang: AddressSanitizer
  • Clang: UndefinedBehaviorSanitizer
  • Clang: static analyzer
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01Does ordinary successful compilation enable ASan?
02For size 3, is index 3 a valid element?
03What should the unsafe fixture's ordinary stdout be treated as?
04Does a clean sanitizer run prove every future input safe?
05When should the overflow guard run?
PREVIOUS LESSONC++ Unit Testing: Normal and Boundary Cases
NEXT LESSONC++ Performance: Benchmark Before Optimizing
ON THIS PAGELearning objectivesDifferent checks look for different kinds of mistakesThree elements have indices zero through twoRun the checked build, not an old executableA clean run only covers the paths that ranBuild a safe checked lookup and verify it with toolsIndependent labReviewKnowledge checkReferences
Course overview