← C Thực Chiến · Toolchain & Kiểm soát chất lượng

Bài 6 · Nâng cao · 20 phút· Cập nhật 11/06/2026

Phân tích tĩnh (clang-tidy)

Biên soạn bởi Nguyễn Anh Tuấn

Phân tích tĩnh (static analysis) cho C: cảnh báo -Wall -Wextra -Werror, clang-tidy với nhóm check bugprone/readability, cppcheck - bắt lỗi trước khi chạy.

Phân tích tĩnh đọc mã nguồn để tìm lỗi trước khi chạy - biến chưa khởi tạo, rò rỉ, so sánh nhầm, code chết. Lớp rẻ nhất và phải bật đầu tiên là cảnh báo của chính trình biên dịch:

canh-bao-trinh-dich

gcc -std=c17 -Wall -Wextra -Werror main.c -o app

# -Wall -Wextra : bat (gan) toan bo canh bao
# -Werror       : coi canh bao la LOI -> buoc phai sua
# Day la "phan tich tinh mien phi" - dung bo qua canh bao!
  • Tĩnh = tìm lỗi qua đọc mã, KHÔNG chạy chương trình (khác test).
  • Lớp 1 (miễn phí): -Wall -Wextra -Werror của gcc/clang.
  • Lớp 2 (sâu hơn): clang-tidy, cppcheck - hàng trăm check.

clang-tidy có hàng trăm check, tên dạng nhom-ten. Chọn nhóm nào bằng file .clang-tidy:

.clang-tidy

# Tat het (-*) roi bat lai cac nhom muon dung:
# clang-analyzer (dong chay: null-deref, leak...), bugprone (mau de sinh loi),
# performance (cham khong can thiet), readability (kho doc).
# LUU Y: dung dat comment # BEN TRONG khoi "Checks: >" - YAML coi do la
# van ban, "Checks" thanh rac va TAT CA check bi tat trong im lang.
Checks: >
  -*,
  clang-analyzer-*,
  bugprone-*,
  performance-*,
  readability-*
WarningsAsErrors: 'clang-analyzer-*,bugprone-*'   # coi nhom nay la loi
  • bugprone-* · performance-* · modernize-* · readability-* · cert-* · clang-analyzer-*.
  • Checks: dùng glob; -* tắt hết rồi bật lại cho gọn.
  • WarningsAsErrors: ép một số nhóm thành lỗi (chặn merge).

Đoạn code dưới có vài vấn đề cài sẵn. Bật/tắt từng nhóm check và xem clang-tidy báo dòng nào:

Bật/tắt nhóm check (như dòng Checks: trong .clang-tidy):
main.c
1 #include <stdlib.h>
2 #include <stdio.h>
3
4 int main(void) {
5 int x;
6 printf("%d\n", x);
7 int *p = malloc(sizeof(int));
8 *p = 42;
9 int y = 0;
10 if (y = 1) {
11 y = y + 86400;
12 }
13 if (y > 10)
14 printf("lon\n");
15 free(p);
16 return 0;
17 }
main.c:6 Biến 'x' được dùng khi chưa khởi tạo
[clang-analyzer-core.uninitialized]
main.c:8 Kết quả malloc gán cho 'p' bị dùng khi chưa kiểm tra NULL
[clang-analyzer-core.NullDereference]
main.c:10 Dùng '=' (gán) trong điều kiện if - có phải ý bạn là '=='?
[bugprone-assignment-in-if-condition]
main.c:11 Số “ma thuật” 86400 - nên đặt thành hằng số có tên
[readability-magic-numbers]
main.c:13 Câu lệnh điều khiển nên có { } quanh thân
[readability-braces-around-statements]

Tắt một nhóm → các cảnh báo thuộc nhóm đó biến mất (đúng cách Checks: hoạt động). Lưu ý: đây là bản demo vài luật - clang-tidy thật có hàng trăm check và phân tích sâu hơn nhiều.

clang-tidy dựng AST thật nên cần biết cách biên dịch (cờ -I, -std…):

chay-clang-tidy

# Cach 1: truyen co sau dau --
clang-tidy main.c -- -Iinclude -std=c17

# Cach 2 (du an CMake): sinh compile_commands.json roi chay ca du an
cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
run-clang-tidy -p build

clang-tidy --fix main.c -- -std=c17   # tu sua mot so check
  • compile_commands.json (CMake sinh) cho clang-tidy biết cờ của từng file.
  • run-clang-tidy chạy song song trên cả dự án.
  • --fix tự sửa các check có “fix-it”; CI: fail nếu còn cảnh báo.

Không công cụ tĩnh nào hoàn hảo. Khi chắc chắn một cảnh báo là báo nhầm, tắt cục bộ - đừng tắt toàn cục:

nolint

int x = ham_dac_biet();  // NOLINT(clang-analyzer-core.uninitialized)

// NOLINTNEXTLINE
int y = mot_macro_la(1, 2);

Kỷ luật

NOLINT phải kèm lý do bạn hiểu cảnh báo và cố ý bỏ qua. Tắt bừa toàn bộ một check làm mất ý nghĩa của công cụ. Mục tiêu là không còn cảnh báo - hoặc sửa, hoặc giải thích.
Chạy trên máy bạn - kỹ năng thật cần gõ lệnh thật

Chạy clang-tidy trên một file có lỗi cài sẵn, viết .clang-tidy chọn nhóm check, và dùng --fix để tự sửa.

$ unzip 06-clang-tidy.zip && cd 06-clang-tidy   # tai zip bang nut o tren
$ clang-tidy buggy.c -- -std=c17           # xem cac canh bao
$ cat .clang-tidy                          # cau hinh nhom check
$ clang-tidy --fix buggy.c -- -std=c17     # tu sua mot so check
$ gcc -Wall -Wextra buggy.c -o app         # so voi canh bao trinh dich
Xem từng file (3) ↡

Câu hỏi thường gặp

Phân tích tĩnh đọc MÃ NGUỒN (không chạy) để tìm các mẫu dễ sai: biến chưa khởi tạo, rò rỉ, so sánh nhầm, code chết… Test thì CHẠY chương trình với dữ liệu cụ thể. Hai cái bổ sung nhau: tĩnh bắt lỗi sớm và rộng; test xác nhận hành vi đúng.

Bài Gỡ lỗi, kiểm thử & CI →

Cảnh báo trình biên dịch là lớp phòng thủ đầu, gần như miễn phí - luôn bật -Wall -Wextra (và -Werror để buộc sửa). clang-tidy đi xa hơn: hàng trăm check chuyên sâu theo nhóm, một số dùng clang static analyzer phân tích theo từng đường chạy (path-sensitive) để bắt null-deref, use-after-free…

Tên check có dạng nhom-ten. Nhóm phổ biến: bugprone-* (mẫu dễ sinh lỗi), performance-* (chậm không cần thiết), readability-* (khó đọc), modernize-* (cú pháp cũ), cert-* (chuẩn an toàn CERT), và clang-analyzer-* (phân tích luồng chạy sâu). Bạn chọn nhóm nào bằng Checks.

Vì nó dựng AST thật - phải biết các cờ (-I include, -std, -D define). Cung cấp qua dấu -- (clang-tidy f.c -- -Iinclude) hoặc, tốt hơn, qua compile_commands.json mà CMake sinh ra (-DCMAKE_EXPORT_COMPILE_COMMANDS=ON).

Bài CMake & Meson →

Không công cụ tĩnh nào hoàn hảo - đôi khi báo nhầm. Tắt CỤC BỘ một dòng bằng // NOLINT (hoặc // NOLINT(ten-check) cho riêng một check, NOLINTNEXTLINE cho dòng kế). Đừng tắt bừa toàn cục - hãy hiểu cảnh báo trước khi bỏ qua.

cppcheck là một bộ phân tích tĩnh khác, không cần biết cờ biên dịch (dễ chạy nhanh), mạnh ở phát hiện lỗi bộ nhớ/biên. Nhiều dự án chạy CẢ clang-tidy lẫn cppcheck vì chúng bắt được tập lỗi hơi khác nhau.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Phân tích tĩnh khác kiểm thử (test) ở điểm cốt lõi nào?

  1. 1

    Tắt một nhóm

    Trong công cụ ở Bước 3, tắt nhóm readability-*. Cảnh báo nào biến mất? Còn lại mấy cái?

    Hoàn thành khi: Số ma thuật (86400) và thiếu {} biến mất → còn 3 (uninitialized, null-deref, gán-trong-if).

  2. 2

    Cảnh báo rẻ nhất

    Lớp phân tích tĩnh “miễn phí”, nên bật đầu tiên trong mọi dự án là gì?

    Hoàn thành khi: Cảnh báo trình biên dịch: -Wall -Wextra (và -Werror để buộc sửa).

  3. 3

    Đọc tên check

    Check tên bugprone-assignment-in-if-condition thuộc nhóm nào, bắt lỗi gì?

    Hoàn thành khi: Nhóm bugprone; bắt việc dùng = (gán) thay vì == trong điều kiện if.

  4. 4

    compile_commands.json

    Vì sao clang-tidy cần compile_commands.json (hoặc cờ sau --)? Sinh nó bằng CMake thế nào?

    Hoàn thành khi: Nó dựng AST thật nên cần cờ biên dịch (-I, -std, -D). Sinh: cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON.

  5. 5

    False positive

    Một dòng bị báo nhầm. Tắt cảnh báo CHỈ cho dòng đó bằng cách nào?

    Hoàn thành khi: Thêm // NOLINT (hoặc // NOLINT(ten-check)) cuối dòng, hoặc // NOLINTNEXTLINE phía trên.

  6. 6

    Tĩnh hay test

    Phân tích tĩnh có thay thế được unit test không? Vì sao?

    Hoàn thành khi: Không. Tĩnh tìm mẫu dễ sai mà không chạy; test xác nhận HÀNH VI đúng với dữ liệu thật. Cần cả hai.