Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 15 – Build systems, testing and tooling

clang-tidy, clang-format and static analysis

Lint C++ with clang-tidy, format it with clang-format in editors and CI, and compare what GCC's -fanalyzer and Clang's analyzer find in the same bugs.

  • Intermediate
  • 15 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: Three tools, three jobs · clang-tidy finds a use-after-move · clang-format fixes the layout in one command · Check yourself. With a pass: 3 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Run clang-tidy with a chosen set of checks on a CMake project
  • Format code automatically with clang-format, in an editor and in CI
  • Compare what GCC's and Clang's static analyzers find in the same code
  • Plan a staged rollout of clang-tidy checks on an existing code base

Before you start

The order program of this lesson compiles without a single warning under -Wall -Wextra -Wpedantic, yet it prints batch for with 0 items, because it reads two parameters after moving them away. Three kinds of tool read C++ source without running it. clang-format rewrites only the layout of the code, its indentation, spaces, line breaks and the order of includes, from a style kept in a .clang-format file. clang-tidy is a linter: A tool that reads source code and reports patterns that are likely bugs, older idioms or poor style, without running the program. clang-tidy is a linter for C and C++.: it compiles each file the way your build does, with the commands it reads from compile_commands.json, and runs checks grouped by purpose, such as bugprone-* for likely bugs, modernize-* for newer idioms and performance-* for needless copies.

Static analysis in the narrow sense follows the paths through a function, so it can see that a pointer is null on one branch or that memory leaks on an early return: Clang’s analyzer (clang++ --analyze, also inside clang-tidy as the clang-analyzer-* checks) and GCC’s -fanalyzer work this way. This lesson runs clang-tidy and clang-format 23.1.2 from Homebrew’s LLVM, Apple clang 21 and GCC 16.

Three tools, three jobs

A good setup formats every file, runs clang-tidy with a chosen set of checks, and uses an analyzer for the bugs that compiler warnings miss. Each tool looks at something different, and only one of them changes your code’s meaning:

clang-format clang-tidy Static analyzers
Looks at Tokens and layout The parsed program, file by file, with the build’s flags The paths through each function, and the functions it calls
Changes the code Yes, the layout only Only with --fix, for checks that offer one Never
Needs A .clang-format file compile_commands.json, and a .clang-tidy file for the choice of checks The compiler: clang++ --analyze, g++ -fanalyzer
Finds Nothing; it formats Bug patterns, old idioms, needless copies, naming and style Leaks, null dereferences, use after free, double frees, uninitialised reads

Static analysis takes longer than the other two jobs and its reports need more judgement, so it usually runs in CI rather than on every save. Formatting is the cheapest job, which is why editors run clang-format whenever a file is saved. The diagram shows what each of the three reads:

The same source files go to clang-format, clang-tidy and the static analyzers; clang-tidy also reads the compile commands CMake writes.Your .cpp and .hpp filesThree tools, three jobsCMake withCMAKE_EXPORT_COMPILE_COMMANDSwrites compile_commands.jsonfor clang-tidyclang-formatreads .clang-formatrewrites the layoutclang-tidyreads compile_commands.jsonand .clang-tidyreports, or fixes with --fixclang++ --analyze, g++ -fanalyzerfollow the paths througheach functionreport onlythe same files

Three tools read the same files for three different jobs

Text description of the diagram

At the top, the project's .cpp and .hpp files have an arrow, labelled "the same files", to a column of three tools.

  1. clang-format reads the .clang-format file and rewrites the layout of the code.
  2. clang-tidy reads compile_commands.json and the .clang-tidy file, and reports findings, or fixes some of them with --fix.
  3. The static analyzers, clang++ --analyze and g++ -fanalyzer, follow the paths through each function and only report.

Below the column, a box for CMake with CMAKE_EXPORT_COMPILE_COMMANDS has an arrow to the tools: CMake writes the compile_commands.json file that clang-tidy reads.

Checkpoint

Check: Formatter, linter and analyzer

  1. Question 1 of 2 Which tool changes only the layout of code, never what it means?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: clang-format

    clang-format rewrites indentation, spaces, line breaks and include order from the style in .clang-format. clang-tidy's fixes can change code, and the analyzers only report.

  2. Question 2 of 2 Which kind of tool can see that memory leaks on an early return of a function?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A static analyzer, because it follows the paths through the function

    Static analysis in the narrow sense follows the paths through a function, so it can see that a pointer is null on one branch or that memory leaks on an early return. Formatting and pattern checks look at code, not paths.

Checks, settings and analyzers to know by name

  • Check groups such as bugprone, modernize, performance, readability and cppcoreguidelines collect checks with one purpose.
  • A .clang-tidy file chooses the checks, the warnings that become errors and the headers that are checked.
  • NOLINT comments silence one finding on one line, naming the check and, by convention, the reason.
  • run-clang-tidy runs clang-tidy over every file of compile_commands.json in parallel.
  • CMAKE_CXX_CLANG_TIDY makes CMake run clang-tidy on each file while it builds.
  • GCC’s -fanalyzer follows paths through C code and, since GCC 16, through simple C++ code too.
  • Clang’s analyzer runs with clang++ --analyze, or as clang-tidy’s clang-analyzer-* checks.
  • git clang-format formats only the lines that a change touches.

clang-tidy finds a use-after-move

This program moves its two parameters into a Batch and then prints from the parameters. It compiles without a single warning under -Wall -Wextra -Wpedantic:

A use-after-move and its CMake project

C++ · tidy/src/orders.cpp

// Collects order lines into a batch, then reports the batch. Compiles without a single warning.
#include <print>
#include <string>
#include <utility>
#include <vector>

struct Batch {
    std::string customer;
    std::vector<std::string> items;
};

Batch make_batch(std::string customer, std::vector<std::string> items) {
    Batch batch{std::move(customer), std::move(items)};
    std::println("batch for {} with {} items", customer, items.size());
    return batch;
}

int main() {
    const Batch b = make_batch("Asha", {"rice", "dal", "ghee"});
    std::println("{} has {} items", b.customer, b.items.size());
}

Text · tidy/CMakeLists.txt

cmake_minimum_required(VERSION 3.28...4.4)
project(tidy LANGUAGES CXX)

set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
# clang-tidy reads the compiler command of each file from build/compile_commands.json.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_executable(orders src/orders.cpp)
add_executable(orders_fixed src/orders_fixed.cpp)
foreach(target orders orders_fixed)
  target_compile_features(${target} PRIVATE cxx_std_23)
  target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
endforeach()
Build, run, and two clang-tidy runs Python · tidy_session.py
from session import run

# Configure once to write compile_commands.json, then run one clang-tidy check on the program and on its fix
# (2>/dev/null leaves out clang-tidy's own counts on standard error, such as "1 warning generated.").
run("""
cd tidy
cmake -S . -B build -G Ninja
""", keep=r"^-- Build files")
run("""
cmake --build build
./build/orders
clang-tidy --version
clang-tidy -p build --checks='-*,bugprone-use-after-move' src/orders.cpp 2>/dev/null
""")
# CMake 4 leaves the SDK to Apple's compiler; Homebrew's clang-tidy needs it in the compile commands.
run("""
cmake -S . -B build -DCMAKE_OSX_SYSROOT=macosx
""", keep=r"^-- Build files")
run("""
grep -m1 -o 'isysroot [^ ]*' build/compile_commands.json
clang-tidy -p build --checks='-*,bugprone-use-after-move' src/orders.cpp 2>/dev/null
clang-tidy -p build --checks='-*,bugprone-use-after-move' src/orders_fixed.cpp 2>/dev/null
""")

Output

$ cd tidy
$ cmake -S . -B build -G Ninja
-- Build files have been written to: build
[8 lines not shown]
$ cmake --build build
[1/4] Building CXX object CMakeFiles/orders.dir/src/orders.cpp.o
[2/4] Building CXX object CMakeFiles/orders_fixed.dir/src/orders_fixed.cpp.o
[3/4] Linking CXX executable orders
[4/4] Linking CXX executable orders_fixed
$ ./build/orders
batch for  with 0 items
Asha has 3 items
$ clang-tidy --version
Homebrew LLVM version 23.1.2
  Optimized build.
$ clang-tidy -p build --checks='-*,bugprone-use-after-move' src/orders.cpp 2>/dev/null
src/orders.cpp:2:10: error: 'print' file not found [clang-diagnostic-error]
    2 | #include <print>
      |          ^~~~~~~
[exit status 1]
$ cmake -S . -B build -DCMAKE_OSX_SYSROOT=macosx
-- Build files have been written to: build
[2 lines not shown]
$ grep -m1 -o 'isysroot [^ ]*' build/compile_commands.json
isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
$ clang-tidy -p build --checks='-*,bugprone-use-after-move' src/orders.cpp 2>/dev/null
src/orders.cpp:14:58: warning: 'items' used after it was moved [bugprone-use-after-move]
   14 |     std::println("batch for {} with {} items", customer, items.size());
      |                                                          ^
src/orders.cpp:13:38: note: move occurred here
   13 |     Batch batch{std::move(customer), std::move(items)};
      |                                      ^
$ clang-tidy -p build --checks='-*,bugprone-use-after-move' src/orders_fixed.cpp 2>/dev/null

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 tidy_session.py

Step through make_batch with the values of that run, then the finding and the miss:

Interactive · step by step Two moves, two reads, one finding Step through make_batch. The left column holds the parameters, the right one the batch they move into.
Two moves, two reads, one finding, step 7 of 7. Showing customer (empty), items (empty), batch.customer (Asha), batch.items (3 strings), clang-tidy, Printed (batch for with 0 items). In focus: batch.customer, batch.items.std::movestd::movecustomeremptyitemsemptybatch.customerAshabatch.items3 stringsclang-tidybugprone-use-after-movePrintedbatch for with 0 items

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Two parameters. make_batch("Asha", {"rice", "dal", "ghee"}) takes both parameters by value, so customer holds Asha and items holds three strings.
  2. The name moves. Line 13 builds batch and moves customer into it. batch.customer now holds Asha, and the parameter is left moved-from: in this run, an empty string.
  3. The items move. The same line moves items into batch.items, which now holds the three strings. The parameter is left empty too.
  4. Read after the move. Line 14 prints from the parameters, not from batch, so the program printed batch for with 0 items: an empty name and a count of 0, with no warning from the compiler.
  5. One finding. With the SDK found, bugprone-use-after-move reported 'items' used after it was moved, and a note pointed at the move on line 13.
  6. One miss. It said nothing about customer. The check counts passing a variable by non-const reference as giving it a new value, and std::println takes its arguments by forwarding reference.
  7. The fix. The fixed file reads from batch instead of the parameters, and clang-tidy is silent on it.

The program ran and printed batch for with 0 items: it read the empty, moved-from parameters. clang-tidy’s first run never reached the check. It stopped at 'print' file not found, because CMake 4 leaves the choice of SDK to Apple’s compiler, so the compilation database: The file compile_commands.json: the exact compiler command of every source file of a build. CMake writes it when CMAKE_EXPORT_COMPILE_COMMANDS is ON, and tools such as clang-tidy and editors read it. More in “CMake for C++: targets, properties and presets” names no SDK, and Homebrew’s clang-tidy cannot find the standard library without one. Configuring with -DCMAKE_OSX_SYSROOT=macosx, the setting CMake’s release notes give for forcing an SDK, put -isysroot into every command, and the second run reported 'items' used after it was moved, with a note showing where the move happened.

clang-tidy flagged items and not customer, although both were read after the move. Its documentation explains why: passing a variable to a function by non-const reference counts as giving it a new value, since the function may be filling an out-parameter, and std::println takes its arguments by forwarding reference. The fixed file reads from batch instead, and clang-tidy is silent on it.

Checkpoint

Check: clang-tidy on a use-after-move

  1. Question 1 of 2 What did the orders program print first, and why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: "batch for with 0 items", because it read the parameters after moving them into the batch

    make_batch moves customer and items into batch, then prints from the parameters, so it printed an empty name and 0 items. The compiler warned about nothing; clang-tidy's bugprone-use-after-move is the check for this.

  2. Question 2 of 2 clang-tidy reported "print file not found" on a project that builds fine. What was missing in the lesson?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The SDK path in compile_commands.json, added by configuring with -DCMAKE_OSX_SYSROOT=macosx

    clang-tidy compiles each file with the commands in compile_commands.json. CMake 4 left the SDK to Apple's compiler, so Homebrew's clang-tidy had no path to the standard library until the configure step named one.

clang-format fixes the layout in one command

format/messy.cpp, before C++ · format/messy.cpp
#include <vector>
#include <string>
#include <algorithm>
// Ranks players by score; written in a hurry.
struct Player{std::string name;int score;};
std::vector<Player> top_three(std::vector<Player> players){
  std::ranges::sort(players,[](const Player&a,const Player&b){return a.score>b.score;});
    if(players.size()>3) players.resize(3);
  return players;}
Check, fix, check again Python · format_session.py
from session import run

# The style file, the check that CI runs (it changes nothing), the fix, and the check again.
run("""
cd format
cat .clang-format
clang-format --version
""")
run("""
clang-format --dry-run --Werror messy.cpp
""", keep=r"error:", max_lines=4)
run("""
clang-format --dry-run messy.cpp 2>&1 | grep -c clang-format-violations
clang-format -i messy.cpp
clang-format --dry-run --Werror messy.cpp
cat messy.cpp
xcrun clang-format --version
""")

Output

$ cd format
$ cat .clang-format
# The project's style: LLVM's, with 4-space indents and 100 columns. clang-format finds this file next to the code.
BasedOnStyle: LLVM
IndentWidth: 4
ColumnLimit: 100
$ clang-format --version
Homebrew clang-format version 23.1.2
$ clang-format --dry-run --Werror messy.cpp
messy.cpp:1:1: error: code should be clang-formatted [-Wclang-format-violations]
messy.cpp:5:14: error: code should be clang-formatted [-Wclang-format-violations]
messy.cpp:5:15: error: code should be clang-formatted [-Wclang-format-violations]
messy.cpp:5:32: error: code should be clang-formatted [-Wclang-format-violations]
[62 lines not shown]
[exit status 1]
$ clang-format --dry-run messy.cpp 2>&1 | grep -c clang-format-violations
22
$ clang-format -i messy.cpp
$ clang-format --dry-run --Werror messy.cpp
$ cat messy.cpp
#include <algorithm>
#include <string>
#include <vector>
// Ranks players by score; written in a hurry.
struct Player {
    std::string name;
    int score;
};
std::vector<Player> top_three(std::vector<Player> players) {
    std::ranges::sort(players, [](const Player &a, const Player &b) { return a.score > b.score; });
    if (players.size() > 3)
        players.resize(3);
    return players;
}
$ xcrun clang-format --version
Apple clang-format version 21.0.0 (clang-2100.3.34.2)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 format_session.py

The same session, one command at a time:

Interactive · stage by stage messy.cpp checked, fixed and checked again Step through the session. Each stage is one command, with what it printed and what it changed.
messy.cpp checked, fixed and checked again, step 5 of 5. Showing .clang-format (LLVM, 4 spaces, 100 columns), clang-format --dry-run (22 places, exit status 1), clang-format -i, The check again (no output, passed), Editors on save. In focus: Editors on save..clang-formatLLVM, 4 spaces, 100 columnsclang-format --dry-run22 places, exit status 1clang-format -irewrites the fileThe check againno output, passedEditors on savethe same style file

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The style. .clang-format asks for LLVM's style with 4-space indents and lines of up to 100 columns. clang-format finds the file next to the code it formats.
  2. The CI check. clang-format --dry-run --Werror messy.cpp changed nothing and ended with exit status 1. It counted 22 places, each reported as code should be clang-formatted.
  3. The fix. clang-format -i messy.cpp rewrote the file in place: the includes sorted, a space before & in const Player &a, and the body of the if on a line of its own.
  4. Checked again. The same check then printed nothing and passed, so a CI job that runs it on this file passes too.
  5. In the editor. Editors such as Visual Studio Code's C/C++ extension format with clang-format and read the same .clang-format, so a file can be formatted each time it is saved.

--dry-run --Werror is the check a CI job runs: it changes nothing and fails when the file is not formatted. Here it counted 22 places, then clang-format -i rewrote the file in place and the same check passed. The result shows the LLVM style’s choices: includes sorted, a space before & in const Player &a, and the body of the if on its own line. Apple’s Command Line Tools have their own clang-format, version 21, reached as xcrun clang-format; editors such as Visual Studio Code’s C/C++ extension also format with clang-format and read the same .clang-format file.

Key idea

clang-format changes how the code looks and nothing else, so it can run on every save, and its --dry-run --Werror check can stop any change that is not formatted.

Checkpoint

Check: clang-format in editors and CI

  1. Question 1 of 2 Which command should a CI job run to fail on unformatted code without changing any file?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: clang-format --dry-run --Werror file.cpp

    --dry-run reports what would change and --Werror turns those reports into errors, so the job fails. In the lesson the check found 22 places in messy.cpp and passed once clang-format -i had rewritten the file.

  2. Question 2 of 2 How many places in messy.cpp did clang-format count as not formatted before the fix?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 22 places

    clang-format --dry-run reported 22 clang-format-violations. After clang-format -i, the same check printed nothing and passed.

Check yourself

Ten questions on the formatter, the linter and the analyzers as this page has shown them. The weakest idea comes first, and each answer is explained the moment you choose it.

Practice that adapts to you

Practise this lesson

10 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 10 What does clang-tidy read to compile each file the way the build does?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: compile_commands.json

    clang-tidy compiles each file the way your build does, with the commands it reads from compile_commands.json, and a .clang-tidy file chooses its checks.

  2. Question 2 of 10 Which of the three jobs usually runs every time a file is saved, and which usually runs in CI?

    Choose one answer.

    Show the answer: Question 2 of 10

    Answer: Formatting on every save; static analysis in CI

    Static analysis takes longer than the other two jobs and its reports need more judgement, so it usually runs in CI. Formatting is the cheapest job, which is why editors run clang-format whenever a file is saved.

  3. Question 3 of 10 What does a NOLINT comment do?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: Silences one finding on one line, naming the check and, by convention, the reason

    NOLINT comments silence one finding on one line, naming the check and, by convention, the reason, so the next reader sees why the finding was accepted.

  4. Question 4 of 10 You want CMake to run clang-tidy on each file while the project builds. Which setting does that?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: CMAKE_CXX_CLANG_TIDY

    CMAKE_CXX_CLANG_TIDY makes CMake run clang-tidy on each file while it builds; run-clang-tidy runs it over every file of compile_commands.json in parallel, as a separate step.

  5. Question 5 of 10 bugprone-use-after-move flagged items but not customer, though both were read after the move. Why?

    Choose one answer.

    Show the answer: Question 5 of 10

    Answer: Passing a variable by non-const reference, as std::println does, counts as giving it a new value

    The check's documentation treats a non-const reference or pointer argument as a possible out-parameter, which reinitialises the variable. std::println takes its arguments by forwarding reference, so customer looked reinitialised; items.size() is a plain read and was reported.

  6. Question 6 of 10 Why did compile_commands.json name no SDK at first, on macOS with CMake 4?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: CMake 4 leaves the choice of SDK to Apple's compiler, so the commands carry no -isysroot

    CMake 4 leaves the choice of SDK to Apple's compiler, so compile_commands.json names none. Configuring with -DCMAKE_OSX_SYSROOT=macosx, the setting CMake's release notes give for forcing an SDK, put -isysroot into every command.

  7. Question 7 of 10 How did the compiler react to the use-after-move in orders.cpp, built with -Wall -Wextra -Wpedantic?

    Choose one answer.

    Show the answer: Question 7 of 10

    Answer: It gave no warning at all

    The program compiles without a single warning under -Wall -Wextra -Wpedantic and prints wrong values. clang-tidy's bugprone-use-after-move found one of the two reads.

  8. Question 8 of 10 Which change did clang-format make to messy.cpp with the LLVM-based style?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: It sorted the includes and put the body of the if on a line of its own

    The result shows the LLVM style's choices: includes sorted, a space before & in const Player &a, and the body of the if on its own line.

  9. Question 9 of 10 A project adopts clang-format, but reformatting every file at once is not wanted yet. Which command formats only the lines a change touches?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: git clang-format

    git clang-format formats only the lines that a change touches, so old files are not rewritten while new and changed lines follow the style.

  10. Question 10 of 10 A team wants CI to fail on unformatted code without touching files, and wants files fixed while developers write. Which pair does both?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: CI runs clang-format --dry-run --Werror; editors format on save with the same .clang-format

    --dry-run --Werror is the check a CI job runs: it changes nothing and fails when a file is not formatted. clang-format -i, or the editor's formatter reading the same .clang-format, fixes files while you write.

Rolling out clang-tidy on a project, one group of checks at a time, with counts Pro: opens with a Pro, Premium or Ultimate pass

Turning on every check at once buries a team in findings it cannot fix in one go. Counting what each group reports on a small project shows which groups pay off first and which mostly express a taste.

In this part:

  • Counting each group on one project
  • Stage 1, and how to treat its findings
  • Checks that need settings before they help

2 runnable examples (C++ (inventory.cpp), C++ (main.cpp), C++ header, Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

What the analyzers find: GCC, Clang and the compiler on five bugs Pro: opens with a Pro, Premium or Ultimate pass

Compiler warnings, Clang’s analyzer and GCC’s analyzer were each given the same five bugs. The results differ more than their documentation suggests, and the reasons are worth knowing before you trust a clean report.

In this part:

  • Five bugs, five tools
  • Why Clang’s analyzer skipped the null pointer
  • Why GCC’s analyzer found everything, then nothing
  • Static and run-time tools see different bugs

2 runnable examples (C++, Python) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Formatting in CI without churn: one pinned version, and options older versions reject Pro: opens with a Pro, Premium or Ultimate pass

A formatter only helps if everyone gets the same output. Two pinned versions of clang-format show how a team ends up reformatting the same lines back and forth, and how to stop it.

In this part:

  • Same style name, different output
  • Options that older versions reject
  • A setup that stays quiet
  • The one big reformatting commit

2 runnable examples (C++, Code, Python) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher · Warm-up

    What is the difference between clang-format, clang-tidy and a static analyzer such as clang --analyze?

    clang-format changes only the layout of code, its indentation, spacing, line breaks and include order, from the style in a .clang-format file, and never what the code means. clang-tidy is a linter: it parses each file with the flags from compile_commands.json and runs checks for bug patterns, needless copies, older idioms and style, some of which can fix the code with --fix. A static analyzer follows the paths through each function, so it can find a leak on an early return, a null pointer on one branch or a read after delete, without running the program. Clang's analyzer also runs inside clang-tidy as the clang-analyzer checks, and GCC has its own with -fanalyzer. Teams usually format on every save, run clang-tidy in CI, and use an analyzer for bugs that warnings miss.

  2. Senior to staff

    Your team turns on clang-tidy with every check and gets 2,300 findings on a 60,000-line code base. How do you roll it out without stopping work, and how do you decide which checks stay?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    The clang-format check in CI keeps failing on files that developers formatted locally, and each fix produces new differences elsewhere. What is going on, and how do you set it up so that it never happens again?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A colleague says a module is safe because clang-tidy and the static analyzer report nothing on it. What can those tools miss, and what would you add before trusting the module?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • clang-format changes layout only, clang-tidy runs checks on the program as the build compiles it, and analyzers follow paths through the code.
  • clang-tidy needs compile_commands.json; on macOS with CMake 4, configure with -DCMAKE_OSX_SYSROOT=macosx so it finds the SDK.
  • A check can miss what it was built for: bugprone-use-after-move treats passing by non-const reference as a new value.
  • Run clang-format --dry-run --Werror in CI, and clang-format -i or the editor’s formatter while you write.

Terms in this lesson

linter
A tool that reads source code and reports patterns that are likely bugs, older idioms or poor style, without running the program. clang-tidy is a linter for C and C++.
static analysis
Following the paths through a function without running the program, so that a tool can report a leak on an early return or a null pointer on one branch. clang++ --analyze and g++ -fanalyzer do it.
use after move
Reading a variable after std::move has handed its contents to another object. The moved-from object is still valid, but its value is not what it was: in this track's example, an empty string and an empty vector.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.