본문으로 건너뛰기 C++ 정적 분석 도구 통합: Clang-Tidy와 Cppcheck로 코드 퀄리티 강제하기

C++ 정적 분석 도구 통합: Clang-Tidy와 Cppcheck로 코드 퀄리티 강제하기

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 미실행, mallocfree 누락 등은 Cppcheck의 메모리 누수 체크로 잡을 수 있습니다. Clang-Tidy의 bugprone-* 체크도 new/delete 패턴을 검사합니다.

시나리오 3: “std::move 후에 객체를 또 쓰고 있었어요”

"이동 후에 벡터를 다시 쓰는 버그가 있었습니다."
"테스트에서는 안 걸렸는데, 특정 경로에서만 터졌습니다."

원인: std::move 후 사용은 이동 후 객체 상태가 미정의이므로 위험합니다. Clang-Tidybugprone-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-Tidyperformance-for-range-copy가 이것을 경고합니다.

시나리오 5: “매직 넘버가 코드에 가득해요”

"if (x > 1024) 같은 코드가 여러 곳에 있는데, 1024가 뭔지 모르겠습니다."
"리팩터링할 때 상수로 바꿔야 하는데, 놓치는 게 많아요."

원인: 매직 넘버는 가독성과 유지보수를 해칩니다. Clang-Tidyreadability-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-movestd::move 후 객체 사용
bugprone-narrowing-conversions축소 변환 (잘림 위험)
modernize-use-nullptr0 대신 nullptr
modernize-use-auto타입 명시 대신 auto
performance-for-range-copyfor-range에서 불필요한 복사
performance-unnecessary-copy-initialization불필요한 복사 초기화
readability-magic-numbers매직 넘버 경고

자동 수정(—fix)

clang-tidy -p build src/**/*.cpp --fix

—fix: 자동 수정 가능한 항목(예: 0nullptr)을 한 번에 고칩니다. 적용 후 빌드·테스트로 회귀가 없는지 확인하세요.

체크별 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 pathinclude 경로

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-TidyCppcheck
의존성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: 수백 개 경고가 한 번에 나와요”

원인: 기존 코드베이스에 정적 분석을 처음 도입했을 때. 해결법:

  1. WarningsAsErrors를 빈 문자열로 두고 경고만 수집
  2. NOLINT로 우선 무시하며, 새 코드부터 규칙 적용
  3. 점진적으로 // 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. 1단계: WarningsAsErrors: '로 경고만 수집, 팀에 공유
  2. 2단계: 새로 추가되는 코드에만 적용 (--line-filter 또는 경로 필터)
  3. 3단계: 핵심 체크(예: bugprone-use-after-move)를 WarningsAsErrors에 추가
  4. 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 Codeclangd 확장 (clang-tidy 내장), Cppcheck 확장
CLionClang-Tidy, Cppcheck 플러그인
VimALE, coc-clangd
Emacsflycheck-clang-tidy
clangdcompile_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)으로 메모리·경합 버그를 잡습니다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

실전 체크리스트

코드 작성 전

  • 이 기법이 현재 문제를 해결하는 최선의 방법인가?
  • 팀원들이 이 코드를 이해하고 유지보수할 수 있는가?
  • 성능 요구사항을 만족하는가?

코드 작성 중

  • 컴파일러 경고를 모두 해결했는가?
  • 엣지 케이스를 고려했는가?
  • 에러 처리가 적절한가?

코드 리뷰 시

  • 코드의 의도가 명확한가?
  • 테스트 경우가 충분한가?
  • 문서화가 되어 있는가? 이 체크리스트를 활용하여 실수를 줄이고 코드 품질을 높이세요.

이 글에서 다루는 키워드 (관련 검색어)

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++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. Clang-Tidy 공식 문서, Cppcheck 매뉴얼을 참고하세요. 한 줄 요약: Clang-Tidy·Cppcheck로 정적 분석을 CI에 넣어 코드 품질을 유지할 수 있습니다. 다음으로 ASan·TSan(#41-2)를 읽어보면 좋습니다. 다음 글: [안정성 확보 #41-2] 런타임 검증: AddressSanitizer와 ThreadSanitizer로 메모리/경합 버그 잡기 이전 글: [DevOps for C++ #40-3] 컨테이너 기반 개발: Docker로 빌드 환경 표준화 및 배포 이미지 최적화

관련 글