C++ 정적 분석 도구 통합: Clang-Tidy와 Cppcheck로 코드 퀄리티 강제하기
들어가며: 실행 전에 미리 잡기
디버거·로깅을 다뤘다면, 41번은 버그가 발생하기 전에 차단하는 시스템을 다룬다. 정적 분석(static analysis—실행하지 않고 소스 코드만 분석해 버그·규칙 위반을 찾는 것)은 소스 코드만 보고 패턴·규칙 위반·잠재적 버그를 찾아낸다.
Clang-Tidy는 Clang 기반의 확장 가능한 린터(코드 스타일·잠재적 오류를 검사하는 도구)/체커로, 모던 C++ 스타일·성능·버그 체크를 수백 개의 체크로 제공한다. Cppcheck는 컴파일러와 독립적으로 동작해 메모리 누수·널 역참조·경계 오류 등에 강하다. 둘 다 CI에 넣으며, 에디터/IDE와 연동하면 커밋 전부터 품질이 유지된다.
이 글에서 다루는 것
- 문제 시나리오: 실제 겪는 “컴파일은 되는데 나중에 터지는” 상황
- Clang-Tidy: 설치·설정 파일(.clang-tidy)·자주 쓰는 체크·자동 수정
- Cppcheck: 옵션·suppression·CI 연동
- 완전한 정적 분석 예제: 복사 후 바로 실행 가능한 프로젝트
- 자주 발생하는 에러와 해결법
- CI 통합: GitHub Actions·PR 시 자동 실행·실패 시 빌드 차단
- 프로덕션 패턴: 대규모 프로젝트·점진적 도입·팀 정책
개념을 잡는 비유
빌드·검사·배포 파이프라인은 공장 검수 라인과 비슷하다. 같은 입력이면 같은 산출물이 나오게 고정하며, Sanitizer·정적 분석은 출하 전 불량 검사 역할을 한다.
1. 문제 시나리오: 왜 정적 분석이 필요한가
시나리오 1: “배포 후 프로덕션에서 크래시해요”
"로컬에서는 잘 되는데, 고객 환경에서 가끔 크래시가 나요."
"재현이 안 돼서 디버깅이 어렵으며, 로그만 봐도 원인을 못 찾아요."
원인: 널 포인터 역참조, use-after-free, 배열 경계 오류 등은 특정 경로에서만 발생한다. 컴파일러는 경고를 안 내며, 테스트는 그 경로를 커버하지 못할 수 있다. 정적 분석은 코드 전체를 스캔해 “이 조건에서 널이 될 수 있다”는 패턴을 찾아준다.
시나리오 2: “스마트 포인터를 쓰는데 메모리 누수가 나요”
"unique_ptr로 바꿨는데 Valgrind에서 여전히 누수가 나와요."
"순환 참조인지, 무언가 놓친 건지 모르겠습니다."
원인: shared_ptr 순환 참조, new 후 예외 발생 시 delete 미실행, malloc 후 free 누락 등은 Cppcheck의 메모리 누수 체크로 잡을 수 있습니다. Clang-Tidy의 bugprone-* 체크도 new/delete 패턴을 검사합니다.
시나리오 3: “std::move 후에 객체를 또 쓰고 있었어요”
"이동 후에 벡터를 다시 쓰는 버그가 있었습니다."
"테스트에서는 안 걸렸는데, 특정 경로에서만 터졌습니다."
원인: std::move 후 사용은 이동 후 객체 상태가 미정의이므로 위험합니다. Clang-Tidy의 bugprone-use-after-move 체크가 정확히 이 패턴을 찾아줍니다.
시나리오 4: “for-range에서 복사가 일어나고 있었어요”
"10만 개 요소를 순회하는데, const auto& 대신 auto로 써서 복사가 발생했습니다."
"프로파일러에서 복사 생성자 호출이 많다고 나왔습니다."
원인: for (auto x : vec)는 x가 복사됩니다. for (const auto& x : vec) 또는 for (auto&& x : vec)가 맞습니다. Clang-Tidy의 performance-for-range-copy가 이것을 경고합니다.
시나리오 5: “매직 넘버가 코드에 가득해요”
"if (x > 1024) 같은 코드가 여러 곳에 있는데, 1024가 뭔지 모르겠습니다."
"리팩터링할 때 상수로 바꿔야 하는데, 놓치는 게 많아요."
원인: 매직 넘버는 가독성과 유지보수를 해칩니다. Clang-Tidy의 readability-magic-numbers로 경고를 받으며, 상수로 바꾸면 됩니다.
시나리오 6: “PR 리뷰에서 스타일 논쟁이 많아요”
"0 대신 nullptr 써야 한다으며, 4칸 들여쓰기 vs 2칸이랑 매번 논쟁해요."
"리뷰어마다 선호가 달라서 일관성이 없습니다."
원인: 스타일·컨벤션을 도구로 강제하면 논쟁이 사라집니다. Clang-Tidy와 Cppcheck를 CI에 넣으며, 통과하면 병합 정책을 두면 됩니다.
해결 방향
flowchart LR
subgraph before["수동 리뷰 (Before)"]
B1[개발자] --> B2[컴파일]
B2 --> B3[테스트]
B3 --> B4[리뷰]
B4 -.->|논쟁·누락| B5[merge]
end
subgraph after["정적 분석 (After)"]
A1[개발자] --> A2[컴파일]
A2 --> A3[Clang-Tidy]
A2 --> A4[Cppcheck]
A3 --> A5[병합 차단]
A4 --> A5
A5 --> A6[테스트]
A6 --> A7[merge]
end
2. Clang-Tidy
설치
# Ubuntu/Debian
sudo apt install clang-tidy
# macOS (Homebrew)
brew install llvm
# clang-tidy는 llvm에 포함됨: /opt/homebrew/opt/llvm/bin/clang-tidy
# Windows (vcpkg)
vcpkg install clang-tools
버전: Clang-Tidy는 Clang 버전에 종속됩니다. 프로젝트가 C++17/20을 쓰면 Clang 10+이 권장됩니다. clang-tidy --version으로 확인하세요.
설정 파일(.clang-tidy)
프로젝트 루트에 .clang-tidy 파일을 두면, 해당 디렉터리와 하위에서 자동으로 적용됩니다.
# .clang-tidy 예시
Checks: >
bugprone-*,
modernize-use-nullptr,
modernize-use-auto,
performance-for-range-copy,
performance-unnecessary-copy-initialization,
readability-magic-numbers,
readability-simplify-boolean-expr
WarningsAsErrors: '
HeaderFilterRegex: '.*'
CheckOptions:
- key: readability-magic-numbers.IgnoredValues
value: '0;1;2;3;4;5;6;7;8;9;10;16;32;64;128;256;512;1024;2048;4096;8192'
설명
- Checks: 켤 체크 목록.
bugprone-*는 잠재적 버그,modernize-*는 모던 C++ 스타일,performance-*는 성능 관련 - WarningsAsErrors: 빈 문자열이면 경고만 내고 빌드는 통과. 점진적으로
bugprone-use-after-move등을 넣어 에러로 승격 가능 - HeaderFilterRegex:
'.*'는 모든 헤더에서도 진단.'(?!.*/third_party/)'로 서드파티 제외 가능 - CheckOptions:
readability-magic-numbers에서 0, 1, 2 등은 일반적으로 허용
compile_commands.json 연동
Clang-Tidy는 include 경로와 매크로 정의를 알아야 정확히 분석합니다. CMake에서 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON으로 빌드하면 compile_commands.json이 생성됩니다.
# CMake로 compile_commands.json 생성
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# build 디렉터리 기준으로 clang-tidy 실행
clang-tidy -p build src/**/*.cpp
-p build: build/compile_commands.json을 읽어 각 소스 파일의 컴파일 옵션을 적용합니다.
자주 쓰는 체크
| 체크 | 설명 |
|---|---|
bugprone-use-after-move | std::move 후 객체 사용 |
bugprone-narrowing-conversions | 축소 변환 (잘림 위험) |
modernize-use-nullptr | 0 대신 nullptr |
modernize-use-auto | 타입 명시 대신 auto |
performance-for-range-copy | for-range에서 불필요한 복사 |
performance-unnecessary-copy-initialization | 불필요한 복사 초기화 |
readability-magic-numbers | 매직 넘버 경고 |
자동 수정(—fix)
clang-tidy -p build src/**/*.cpp --fix
—fix: 자동 수정 가능한 항목(예: 0 → nullptr)을 한 번에 고칩니다. 적용 후 빌드·테스트로 회귀가 없는지 확인하세요.
체크별 Before/After 예시
bugprone-use-after-move:
// ❌ Before: 이동 후 사용
std::vector<int> vec = get_data();
std::vector<int> other = std::move(vec);
vec.push_back(42); // Clang-Tidy: use-after-move
// ✅ After: 이동 후 사용하지 않음
std::vector<int> vec = get_data();
std::vector<int> other = std::move(vec);
other.push_back(42);
performance-for-range-copy:
// ❌ Before: 요소 복사
for (auto item : large_vector) {
process(item); // item이 복사됨
}
// ✅ After: 참조로 순회
for (const auto& item : large_vector) {
process(item);
}
modernize-use-nullptr:
// ❌ Before
int* p = 0;
if (ptr == NULL) { }
// ✅ After (--fix로 자동 수정 가능)
int* p = nullptr;
if (ptr == nullptr) { }
실행 가능 예제
// demo_clang_tidy.cpp: clang-tidy로 검사해 보세요
// clang-tidy demo_clang_tidy.cpp -- -std=c++17 -I.
#include <iostream>
#include <vector>
#include <memory>
int main() {
int* p = 0; // modernize-use-nullptr 권장
(void)p;
std::vector<int> vec = {1, 2, 3};
for (auto x : vec) { // performance-for-range-copy: 복사 대신 const auto&
std::cout << x << std::endl;
}
std::unique_ptr<int> ptr = std::make_unique<int>(42);
std::cout << "clang-tidy로 이 파일을 검사해 보세요.\n";
return 0;
}
# 단일 파일 검사 (compile_commands 없이)
clang-tidy demo_clang_tidy.cpp -- -std=c++17 -I.
3. Cppcheck
컴파일러 독립 정적 분석
Cppcheck는 컴파일하지 않고 소스만 분석합니다. 메모리 누수, 널 포인터 역참조, 배열 경계, 미초기화 변수 등에 강하며, Clang-Tidy와 보완적 관계입니다.
설치
# Ubuntu/Debian
sudo apt install cppcheck
# macOS
brew install cppcheck
# Windows (vcpkg)
vcpkg install cppcheck
기본 실행
# 기본 검사
cppcheck src/
# 확장 체크 (메모리 누수, 널 역참조 등)
cppcheck --enable=all src/
# include 경로 지정
cppcheck --enable=all -I./include -I./third_party src/
# 시스템 헤더 누락 경고 무시
cppcheck --enable=all --suppress=missingIncludeSystem -I./include src/
주요 옵션
| 옵션 | 설명 |
|---|---|
--enable=all | 모든 확장 체크 (메모리, 널, 미초기화 등) |
--enable=warning | 경고 수준 |
--error-exitcode=1 | 경고/에러 시 비 zero 종료 (CI용) |
-j N | 병렬 분석 (N = CPU 코어 수) |
--suppress=ID | 특정 경고 무시 |
-I path | include 경로 |
Suppression
오탐이 확실할 때만 사용하며, 가능하면 코드를 고치는 쪽이 좋습니다.
// cppcheck-suppress 인라인 주석
void foo() {
int* p = (int*)0x1234; // cppcheck-suppress nullPointer
(void)p;
}
# --suppress로 파일/라인별 무시
cppcheck --suppress=missingInclude:external.h --suppress=unusedFunction:util.cpp src/
실행 가능 예제
// demo_cppcheck.cpp: cppcheck로 검사해 보세요
// cppcheck --enable=all demo_cppcheck.cpp
#include <iostream>
#include <vector>
int main() {
int* p = nullptr;
if (p) {
*p = 42; // Cppcheck: nullPointer (이 경로에서는 안 됨)
}
std::vector<int> vec(10);
vec[10] = 0; // Cppcheck: arrayIndexOutOfBounds
int x; // Cppcheck: uninitvar (미초기화)
std::cout << x << std::endl;
return 0;
}
cppcheck --enable=all demo_cppcheck.cpp
Clang-Tidy vs Cppcheck 비교
| 항목 | Clang-Tidy | Cppcheck |
|---|---|---|
| 의존성 | Clang/LLVM 필요 | 독립 실행 |
| 강점 | 모던 C++, 성능, 스타일 | 메모리, 널, 경계 |
| compile_commands | 필수 (정확한 분석) | 불필요 |
| 자동 수정 | —fix 지원 | 미지원 |
| 컴파일 | 컴파일 옵션 활용 | 소스만 분석 |
| 권장 | 둘 다 사용 (보완적) | 둘 다 사용 (보완적) |
4. 완전한 정적 분석 예제
프로젝트 구조
static-analysis-demo/
├── CMakeLists.txt
├── .clang-tidy
├── cppcheck.cfg
├── src/
│ ├── main.cpp
│ └── util.cpp
└── include/
└── util.h
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(StaticAnalysisDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_executable(demo
src/main.cpp
src/util.cpp
)
target_include_directories(demo PRIVATE ${CMAKE_SOURCE_DIR}/include)
.clang-tidy
Checks: 'bugprone-*,modernize-use-nullptr,performance-*,readability-magic-numbers'
WarningsAsErrors: '
HeaderFilterRegex: '.*'
cppcheck.cfg (선택)
<?xml version="1.0"?>
<project>
<include>
<path name="include"/>
</include>
</project>
src/main.cpp
#include <iostream>
#include <vector>
#include <memory>
#include "util.h"
int main() {
int* p = 0; // Clang-Tidy: modernize-use-nullptr
(void)p;
std::vector<int> data = {1, 2, 3, 4, 5};
for (auto x : data) { // Clang-Tidy: performance-for-range-copy
std::cout << x << " ";
}
std::cout << "\n";
if (process(data) > 1024) { // Clang-Tidy: readability-magic-numbers
std::cout << "Large result\n";
}
return 0;
}
src/util.cpp
#include "util.h"
#include <numeric>
int process(const std::vector<int>& vec) {
return std::accumulate(vec.begin(), vec.end(), 0);
}
include/util.h
#pragma once
#include <vector>
int process(const std::vector<int>& vec);
실행 스크립트
#!/bin/bash
# run-static-analysis.sh
set -e
# 1. 빌드 (compile_commands.json 생성)
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build
# 2. Clang-Tidy
echo "=== Clang-Tidy ==="
clang-tidy -p build src/*.cpp
# 3. Cppcheck
echo "=== Cppcheck ==="
cppcheck --enable=all --suppress=missingIncludeSystem \
-I./include --error-exitcode=1 src/
run-clang-tidy로 전체 프로젝트 검사
# run-clang-tidy.py (LLVM에 포함) 또는 pip install run-clang-tidy
# 병렬로 전체 소스 검사
run-clang-tidy -p build -j 4 -header-filter='.*' src/
# 변경된 파일만 검사 (incremental)
git diff --name-only main | grep '\.cpp$' | xargs clang-tidy -p build
line-filter로 특정 라인만 검사
# PR에서 변경된 라인만 검사 (대규모 프로젝트용)
clang-tidy -p build src/main.cpp --line-filter='[{"name":"src/main.cpp","lines":[[10,20],[30,40]]}]'
5. 자주 발생하는 에러와 해결법
문제 1: “clang-tidy: Could not find compile_commands.json”
원인: -p build 또는 -p .로 지정한 디렉터리에 compile_commands.json이 없음.
해결법:
# CMake로 compile_commands.json 생성
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
# 또는 프로젝트 루트에 심볼릭 링크
ln -sf build/compile_commands.json compile_commands.json
문제 2: “clang-tidy: ‘vector’ file not found”
원인: include 경로가 없음. compile_commands.json이 있으면 자동으로 적용되지만, 단일 파일 검사 시에는 -- 뒤에 옵션을 줘야 함.
해결법:
clang-tidy src/main.cpp -- -std=c++17 -I./include -I/usr/include
문제 3: “Cppcheck: Too many #ifdef configurations”
원인: #ifdef가 많아서 조합이 폭발적으로 늘어남.
해결법:
# --max-configs로 제한
cppcheck --enable=all --max-configs=8 src/
문제 4: “Cppcheck: missingIncludeSystem”
원인: 시스템 헤더(<iostream> 등)를 찾지 못함.
해결법:
cppcheck --suppress=missingIncludeSystem -I./include src/
문제 5: “Clang-Tidy: 수백 개 경고가 한 번에 나와요”
원인: 기존 코드베이스에 정적 분석을 처음 도입했을 때. 해결법:
- WarningsAsErrors를 빈 문자열로 두고 경고만 수집
- NOLINT로 우선 무시하며, 새 코드부터 규칙 적용
- 점진적으로
// NOLINT제거 및 수정
int* p = 0; // NOLINT(modernize-use-nullptr) - 레거시 코드, 추후 수정
문제 6: “run-clang-tidy가 없어요”
원인: run-clang-tidy는 Clang/LLVM 빌드 시 함께 포함되는데, 일부 패키지에는 없음.
해결법:
# run-clang-tidy.py 사용 (LLVM 소스에 포함)
# 또는 수동으로 clang-tidy 실행
find src -name "*.cpp" | xargs -I {} clang-tidy --config-file=.clang-tidy -p build {}
문제 7: “Cppcheck: 오탐(false positive)이 많아요”
원인: 특정 패턴에서 Cppcheck가 잘못된 경고를 냄. 해결법:
// cppcheck-suppress로 해당 라인만 무시
void* ptr = get_address_from_hardware();
// cppcheck-suppress invalidPointer
int* p = (int*)ptr;
문제 8: “Clang-Tidy: 특정 체크만 끄고 싶어요”
해결법:
# .clang-tidy에서 Checks에서 제외
Checks: 'bugprone-*,-bugprone-easily-swappable-parameters,modernize-*'
- 접두사로 해당 체크를 비활성화합니다.
문제 9: “CI에서 clang-tidy가 너무 오래 걸려요”
해결법
- 변경된 파일만 검사 (
git diff기반) -j N으로 병렬화compile_commands.json캐시- Clang-Tidy job을 별도로 두고 병렬 실행
문제 10: “third_party에서 경고가 많이 나와요”
해결법:
# .clang-tidy
HeaderFilterRegex: '(?!.*/third_party/)(?!.*/vcpkg_installed/)'
6. CI 통합
GitHub Actions
# .github/workflows/static-analysis.yml
name: Static Analysis
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
clang-tidy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y clang-tidy cmake build-essential
- name: Configure
run: cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Run Clang-Tidy
run: |
find src -name "*.cpp" | while read f; do
clang-tidy -p build "$f" --warnings-as-errors="*"
done
cppcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Cppcheck
run: sudo apt-get update && sudo apt-get install -y cppcheck
- name: Run Cppcheck
run: |
cppcheck --enable=all --suppress=missingIncludeSystem \
-I./include --error-exitcode=1 -j 4 src/
GitLab CI
# .gitlab-ci.yml
static-analysis:
stage: test
image: ubuntu:22.04
before_script:
- apt-get update && apt-get install -y clang-tidy cppcheck cmake g++
- cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
script:
- find src -name "*.cpp" -exec clang-tidy -p build {} \;
- cppcheck --enable=all --suppress=missingIncludeSystem -I./include --error-exitcode=1 src/
run-clang-tidy.py 사용 (병렬)
# run-clang-tidy.py는 LLVM/Clang 소스에 포함
# pip install run-clang-tidy 로 설치 가능
run-clang-tidy -p build -j 4 src/
브랜치 보호 규칙
- main 병합 전 정적 분석 통과 필수
- PR에서 실패 시 리뷰어가 승인해도 merge 불가
- 팀 정책: “Clang-Tidy·Cppcheck 모두 0 경고”
변경된 파일만 검사하는 CI (빠른 피드백)
# .github/workflows/static-analysis-incremental.yml
name: Static Analysis (Incremental)
on:
pull_request:
branches: [main]
jobs:
incremental:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # main과 diff 위해
- name: Install tools
run: sudo apt-get install -y clang-tidy cppcheck cmake g++
- name: Configure
run: cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Get changed files
id: changed
run: |
FILES=$(git diff --name-only origin/main...HEAD | grep '\.cpp$' || true)
echo "files<<EOF" >> $GITHUB_OUTPUT
echo "$FILES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Run Clang-Tidy on changed files
run: |
for f in ${{ steps.changed.outputs.files }}; do
[ -f "$f" ] && clang-tidy -p build "$f" --warnings-as-errors="*" || true
done
- name: Run Cppcheck on changed files
run: |
FILES="${{ steps.changed.outputs.files }}"
[ -n "$FILES" ] && cppcheck --enable=all --suppress=missingIncludeSystem \
-I./include --error-exitcode=1 $FILES || true
CMake 타겟으로 정적 분석 실행
# CMakeLists.txt에 추가
find_program(CLANG_TIDY_BIN clang-tidy)
find_program(CPPCHECK_BIN cppcheck)
if(CLANG_TIDY_BIN)
add_custom_target(clang-tidy
COMMAND ${CLANG_TIDY_BIN} -p ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src/*.cpp
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running clang-tidy"
)
endif()
if(CPPCHECK_BIN)
add_custom_target(cppcheck
COMMAND ${CPPCHECK_BIN} --enable=all --suppress=missingIncludeSystem
-I${CMAKE_SOURCE_DIR}/include --error-exitcode=1 ${CMAKE_SOURCE_DIR}/src/
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMENT "Running cppcheck"
)
endif()
# 사용: cmake --build build --target clang-tidy
7. 프로덕션 패턴
패턴 1: 점진적 도입
flowchart LR A[1단계: 경고만] --> B[2단계: 신규 코드] B --> C[3단계: WarningsAsErrors] C --> D[4단계: 전체 적용]
- 1단계:
WarningsAsErrors: '로 경고만 수집, 팀에 공유 - 2단계: 새로 추가되는 코드에만 적용 (
--line-filter또는 경로 필터) - 3단계: 핵심 체크(예:
bugprone-use-after-move)를WarningsAsErrors에 추가 - 4단계: 전체 프로젝트에 적용, 레거시 코드 점진적 수정
패턴 2: 체크 그룹별 분리
# .clang-tidy - 팀별로 다른 설정
# 예: 스타일은 경으며, 버그는 에러
Checks: 'bugprone-*,modernize-*,performance-*,readability-*'
WarningsAsErrors: 'bugprone-use-after-move,bugprone-narrowing-conversions'
패턴 3: 서드파티 제외
# .clang-tidy
HeaderFilterRegex: '(?!.*/third_party/)(?!.*/external/)'
헤더가 third_party/ 또는 external/에 있으면 진단하지 않습니다.
패턴 4: pre-commit 훅
#!/bin/bash
# .git/hooks/pre-commit
set -e
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 2>/dev/null || true
for f in $(git diff --cached --name-only --diff-filter=ACM | grep '\.cpp$'); do
[ -f "$f" ] && clang-tidy -p build "$f"
done
cppcheck --enable=all --suppress=missingIncludeSystem -I./include $(git diff --cached --name-only --diff-filter=ACM | grep '\.cpp$' | tr '\n' ' ')
패턴 5: 대규모 프로젝트
- incremental: 변경된 파일만 검사 (
git diff기반) - 캐시: CI에서
compile_commands.json캐시 - 병렬:
run-clang-tidy -j 8또는cppcheck -j 8
패턴 6: 에디터 연동
| 에디터 | 방법 |
|---|---|
| VS Code | clangd 확장 (clang-tidy 내장), Cppcheck 확장 |
| CLion | Clang-Tidy, Cppcheck 플러그인 |
| Vim | ALE, coc-clangd |
| Emacs | flycheck-clang-tidy |
clangd는 compile_commands.json을 읽어 저장 시 자동으로 Clang-Tidy를 실행합니다. |
패턴 7: Cppcheck 설정 파일로 프로젝트 표준화
<?xml version="1.0"?>
<!-- cppcheck.cfg: 프로젝트 루트에 두면 cppcheck가 자동 인식 -->
<project>
<include>
<path name="include"/>
<path name="third_party/optional"/>
</include>
<exclude>
<path name="third_party/legacy"/>
</exclude>
</project>
# cppcheck.cfg가 있으면 -I 옵션 없이도 include 경로 적용
cppcheck --enable=all src/
패턴 8: NOLINT 정책
레거시 코드에서 일시적으로 무시할 때:
// NOLINT: 전체 라인 무시
int* p = 0; // NOLINT
// NOLINT(체크이름): 특정 체크만 무시
int* p = 0; // NOLINT(modernize-use-nullptr)
// NOLINTNEXTLINE: 다음 라인 무시
// NOLINTNEXTLINE(modernize-use-nullptr)
int* p = 0;
정책: NOLINT는 임시로만 사용하며, 이슈를 생성해 추후 제거하는 것을 권장합니다.
구현 체크리스트
- [ ] .clang-tidy 생성 및 Checks 설정
- [ ] CMake에 CMAKE_EXPORT_COMPILE_COMMANDS=ON 추가
- [ ] compile_commands.json 생성 확인
- [ ] clang-tidy -p build로 로컬 검사
- [ ] cppcheck --enable=all로 로컬 검사
- [ ] CI 워크플로에 Clang-Tidy·Cppcheck job 추가
- [ ] --error-exitcode=1 / --warnings-as-errors 설정
- [ ] 브랜치 보호 규칙과 연동
- [ ] 에디터(clangd, Cppcheck 확장) 연동
- [ ] 팀 정책 문서화 ("정적 분석 통과 시 병합")
8. 정리
| 도구 | 역할 |
|---|---|
| Clang-Tidy | 모던 C++·버그·성능 체크, compile_commands 연동·자동 수정 |
| Cppcheck | 컴파일 없이 메모리·널·경계 등 검사, CI에 병렬 실행 |
| 통합 | CI에서 실패 시 빌드 차단, 에디터에서 실시간 진단 |
| 핵심: “컴파일은 되는데 나중에 터지는” 버그를 실행 전에 미리 잡으려면, Clang-Tidy와 Cppcheck를 CI·에디터에 통합하며, 정책으로 강제하는 것이 효과적입니다. 41-2에서는 런타임 검증(AddressSanitizer, ThreadSanitizer)으로 메모리·경합 버그를 잡습니다. |
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ [[nodiscard]] 완벽 가이드 | 반환값 무시 방지·에러 코드·RAII·사유 메시지 [실전]
- Rust vs C++ 메모리 안전성 | 컴파일러 오류 차이 [#47-3]
- C++ Static Analysis | “정적 분석” 가이드
실전 체크리스트
코드 작성 전
- 이 기법이 현재 문제를 해결하는 최선의 방법인가?
- 팀원들이 이 코드를 이해하고 유지보수할 수 있는가?
- 성능 요구사항을 만족하는가?
코드 작성 중
- 컴파일러 경고를 모두 해결했는가?
- 엣지 케이스를 고려했는가?
- 에러 처리가 적절한가?
코드 리뷰 시
- 코드의 의도가 명확한가?
- 테스트 경우가 충분한가?
- 문서화가 되어 있는가? 이 체크리스트를 활용하여 실수를 줄이고 코드 품질을 높이세요.
이 글에서 다루는 키워드 (관련 검색어)
clang-tidy, cppcheck, 정적 분석, C++ 코드 품질, CI 통합 등으로 검색하시면 이 글이 도움이 됩니다.
자주 묻는 질문 (FAQ)
Q. Clang-Tidy와 Cppcheck를 둘 다 써야 하나요?
A. 둘 다 쓰는 것을 권장합니다. Clang-Tidy는 모던 C++·성능·스타일에 강하며, Cppcheck는 메모리·널·경계 등에 강합니다. 보완적 관계입니다.
Q. 기존 코드베이스에 처음 도입할 때 어떻게 하나요?
A. WarningsAsErrors를 빈 문자열로 두고 경고만 수집한 뒤, 새 코드부터 점진적으로 적용하며, NOLINT로 레거시 코드는 우선 무시하는 방식을 추천합니다.
Q. CI에서 실패 시 빌드 차단을 어떻게 하나요?
A. clang-tidy --warnings-as-errors="*"와 cppcheck --error-exitcode=1을 사용하면, 경고/에러 시 exit code가 0이 아니어서 CI가 실패로 처리합니다. 브랜치 보호 규칙과 함께 두면 병합 전에 통과해야 합니다.
Q. 프로덕션 빌드에 정적 분석을 넣나요?
A. 정적 분석은 컴파일 시 또는 별도 단계로 실행합니다. 런타임 오버헤드는 없습니다. CI·에디터에서 실행하며, 프로덕션 바이너리에는 영향이 없습니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.