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

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

이 글의 핵심

16번 디버깅의 연장선. Clang-Tidy와 Cppcheck를 CI·에디터에 통합해 버그가 발생하기 전에 코드 퀄리티를 강제하는 방법을 다룹니다. 문제 시나리오, 완전한 예제, CI 통합, 프로덕션 패턴까지.

들어가며: 실행 전에 미리 잡기

”컴파일은 되는데 나중에 터진다”

16번에서 디버거·로깅을 다뤘다면, 41번은 버그가 발생하기 전에 차단하는 시스템을 다룹니다. 정적 분석(static analysis—실행하지 않고 소스 코드만 분석해 버그·규칙 위반을 찾는 것)은 소스 코드만 보고 패턴·규칙 위반·잠재적 버그를 찾아냅니다.
Clang-Tidy는 Clang 기반의 확장 가능한 린터(코드 스타일·잠재적 오류를 검사하는 도구)/체커로, 모던 C++ 스타일·성능·버그 체크를 수백 개의 체크로 제공합니다. Cppcheck는 컴파일러와 독립적으로 동작해 메모리 누수·널 역참조·경계 오류 등에 강합니다. 둘 다 CI에 넣고, 에디터/IDE와 연동하면 커밋 전부터 품질이 유지됩니다.

이 글에서 다루는 것:

  • 문제 시나리오: 실제 겪는 “컴파일은 되는데 나중에 터지는” 상황
  • Clang-Tidy: 설치·설정 파일(.clang-tidy)·자주 쓰는 체크·자동 수정
  • Cppcheck: 옵션·suppression·CI 연동
  • 완전한 정적 분석 예제: 복사 후 바로 실행 가능한 프로젝트
  • 자주 발생하는 에러와 해결법
  • CI 통합: GitHub Actions·PR 시 자동 실행·실패 시 빌드 차단
  • 프로덕션 패턴: 대규모 프로젝트·점진적 도입·팀 정책

개념을 잡는 비유

빌드·검사·배포 파이프라인은 공장 검수 라인과 비슷합니다. 같은 입력이면 같은 산출물이 나오게 고정하고, Sanitizer·정적 분석은 출하 전 불량 검사 역할을 합니다.


목차

  1. 문제 시나리오: 왜 정적 분석이 필요한가
  2. Clang-Tidy
  3. Cppcheck
  4. 완전한 정적 분석 예제
  5. 자주 발생하는 에러와 해결법
  6. CI 통합
  7. 프로덕션 패턴
  8. 정리

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)으로 메모리·경합 버그를 잡습니다.


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

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

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

Q. 더 깊이 공부하려면?

A. Clang-Tidy 공식 문서, Cppcheck 매뉴얼을 참고하세요.

한 줄 요약: Clang-Tidy·Cppcheck로 정적 분석을 CI에 넣어 코드 품질을 유지할 수 있습니다. 다음으로 ASan·TSan(#41-2)를 읽어보면 좋습니다.

다음 글: [안정성 확보 #41-2] 런타임 검증: AddressSanitizer와 ThreadSanitizer로 메모리/경합 버그 잡기

이전 글: [DevOps for C++ #40-3] 컨테이너 기반 개발: Docker로 빌드 환경 표준화 및 배포 이미지 최적화


관련 글

  • C++ volatile 완벽 가이드 | MMIO·ISR·메모리 맵 레지스터·atomic과의 차이 [실전]
  • C++ 런타임 검증: AddressSanitizer와 ThreadSanitizer 완벽 가이드 [#41-2]
  • C++ [[nodiscard]] 완벽 가이드 | 반환값 무시 방지·에러 코드·RAII·사유 메시지 [실전]
  • C++ Fuzz Testing: 예상치 못한 입력값으로 프로그램의 견고함 테스트하기 [#41-3]
  • C++ 정적 분석 도구 | Clang-Tidy·Cppcheck·SonarQube [#53-5]