C++ Static Analysis | "정적 분석" 가이드
이 글의 핵심
C++ Static Analysis에 대한 실전 가이드입니다.
들어가며
정적 분석(Static Analysis)은 코드를 실행하지 않고 분석하여 버그, 코드 스멜, 보안 취약점을 찾는 강력한 도구입니다. Clang-Tidy, Cppcheck 등을 활용하면 코드 품질을 크게 향상시킬 수 있습니다.
1. Clang-Tidy
설치 및 기본 사용
# Ubuntu/Debian
sudo apt-get install clang-tidy
# macOS
brew install llvm
# 기본 실행
clang-tidy program.cpp -- -std=c++17
# 자동 수정
clang-tidy -fix program.cpp -- -std=c++17
# 특정 검사만
clang-tidy -checks='modernize-*' program.cpp -- -std=c++17
예제 코드 분석
정적 분석 도구가 어떤 문제를 찾아내는지 실제 예제로 확인해봅시다:
// bad_code.cpp
#include <iostream>
#include <vector>
void processData() {
// 문제 1: nullptr 역참조 (Null Pointer Dereference)
int* ptr = nullptr;
*ptr = 42; // 크래시 발생! nullptr을 역참조하면 세그멘테이션 폴트
// 문제 2: 배열 범위 초과 (Buffer Overflow)
int arr[10]; // 인덱스 0~9만 유효
for (int i = 0; i <= 10; i++) { // i=10일 때 범위 초과!
arr[i] = i; // 미정의 동작 (Undefined Behavior)
// 메모리 오염, 크래시, 보안 취약점 발생 가능
}
// 문제 3: 메모리 누수 (Memory Leak)
int* data = new int(100);
// delete data;를 하지 않음
// 함수가 끝나도 메모리가 해제되지 않아 누수 발생
// 문제 4: 사용하지 않는 변수 (Dead Code)
int unused = 42;
// 선언만 하고 사용하지 않음 → 코드 낭비
// 문제 5: 비효율적 복사 (Unnecessary Copy)
std::vector<int> vec = {1, 2, 3};
for (int x : vec) { // 매 반복마다 요소를 복사
std::cout << x << std::endl;
}
// 올바른 방법: for (const int& x : vec) 또는 for (int x : vec) (작은 타입)
}
Clang-Tidy 실행 결과:
$ clang-tidy bad_code.cpp -- -std=c++17
# 경고 1: nullptr 역참조 감지
bad_code.cpp:7:5: warning: Dereference of null pointer [clang-analyzer-core.NullDereference]
*ptr = 42;
^
# 설명: ptr이 nullptr인데 *ptr로 접근하면 크래시 발생
# 경고 2: 배열 범위 초과 감지
bad_code.cpp:11:23: warning: Value stored to 'i' is never read [clang-analyzer-deadcode.DeadStores]
for (int i = 0; i <= 10; i++) {
^
# 설명: i가 10일 때 arr[10]은 범위를 벗어남 (arr[0]~arr[9]만 유효)
# 경고 3: 메모리 누수 감지
bad_code.cpp:16:16: warning: Potential memory leak [clang-analyzer-cplusplus.NewDeleteLeaks]
int* data = new int(100);
^
# 설명: new로 할당한 메모리를 delete하지 않음
# 경고 4: 사용하지 않는 변수 감지
bad_code.cpp:19:9: warning: unused variable 'unused' [clang-diagnostic-unused-variable]
int unused = 42;
^
# 설명: 변수를 선언했지만 사용하지 않음
# 경고 5: 비효율적 복사 감지
bad_code.cpp:23:14: warning: loop variable is copied but only used as const reference [performance-for-range-copy]
for (int x : vec) {
^
# 설명: 매 반복마다 요소를 복사하는 대신 const 참조를 사용하면 성능 향상
# 제안: for (const int& x : vec) 또는 for (int x : vec) (int는 작아서 복사가 빠름)
정적 분석의 가치:
- 런타임에 발생할 버그를 컴파일 타임에 미리 발견
- 코드 리뷰 전에 기본적인 문제 해결
- 보안 취약점 조기 발견
2. .clang-tidy 설정
기본 설정 파일
# .clang-tidy
Checks: >
-*,
bugprone-*,
modernize-*,
performance-*,
readability-*,
-modernize-use-trailing-return-type
CheckOptions:
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: camelBack
- key: readability-identifier-naming.VariableCase
value: camelBack
- key: readability-identifier-naming.ConstantCase
value: UPPER_CASE
검사 카테고리
# 버그 탐지
bugprone-*
# 모던 C++ 변환
modernize-*
- modernize-use-nullptr
- modernize-use-auto
- modernize-use-override
- modernize-loop-convert
# 성능 개선
performance-*
- performance-move-const-arg
- performance-unnecessary-copy-initialization
- performance-for-range-copy
# 가독성
readability-*
- readability-identifier-naming
- readability-magic-numbers
- readability-braces-around-statements
3. Cppcheck
설치 및 기본 사용
# Ubuntu/Debian
sudo apt-get install cppcheck
# macOS
brew install cppcheck
# 기본 실행
cppcheck program.cpp
# 모든 검사 활성화
cppcheck --enable=all program.cpp
# 특정 검사
cppcheck --enable=warning,performance program.cpp
# XML 리포트
cppcheck --xml program.cpp 2> report.xml
# 디렉토리 전체 검사
cppcheck --enable=all src/
예제
// test.cpp
#include <iostream>
#include <vector>
void processArray() {
int arr[10];
// 문제: 초기화되지 않은 배열
for (int i = 0; i < 10; i++) {
std::cout << arr[i] << std::endl;
}
}
void processVector() {
std::vector<int> vec;
// 문제: 범위 초과
std::cout << vec[0] << std::endl;
}
Cppcheck 실행:
$ cppcheck --enable=all test.cpp
[test.cpp:9]: (error) Uninitialized variable: arr
[test.cpp:17]: (error) Out of bounds access in expression 'vec[0]'
4. 실전 예제: CI/CD 통합
GitHub Actions
# .github/workflows/static-analysis.yml
name: Static Analysis
on: [push, pull_request]
jobs:
clang-tidy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Clang-Tidy
run: |
sudo apt-get update
sudo apt-get install -y clang-tidy
- name: Run Clang-Tidy
run: |
find src -name "*.cpp" -exec clang-tidy {} -- -std=c++17 \;
cppcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Cppcheck
run: sudo apt-get install -y cppcheck
- name: Run Cppcheck
run: |
cppcheck --enable=all --error-exitcode=1 src/
CMake 통합
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
# Clang-Tidy 통합
set(CMAKE_CXX_CLANG_TIDY
clang-tidy;
-checks=bugprone-*,modernize-*,performance-*;
-header-filter=.*
)
add_executable(myapp main.cpp)
5. 자주 발생하는 문제
문제 1: 거짓 양성 (False Positive)
// 정적 분석 경고 (거짓 양성)
int* getStaticPointer() {
static int value = 10;
return &value; // 경고: 지역 변수 주소 반환
}
// 실제로는 안전 (static 변수)
// 억제 방법
// NOLINTNEXTLINE(clang-analyzer-core.StackAddressEscape)
int* getStaticPointer() {
static int value = 10;
return &value;
}
문제 2: 너무 많은 경고
# ❌ 모든 검사 (압도적)
clang-tidy -checks=* program.cpp
# ✅ 점진적 적용
# 1단계: 버그만
clang-tidy -checks=bugprone-* program.cpp
# 2단계: 모던화
clang-tidy -checks=bugprone-*,modernize-* program.cpp
# 3단계: 성능
clang-tidy -checks=bugprone-*,modernize-*,performance-* program.cpp
문제 3: 레거시 코드
// 레거시 코드에 정적 분석 적용 전략:
// 1. 새 코드부터 적용
// 2. 중요한 모듈부터
// 3. 우선순위 설정 (버그 > 성능 > 스타일)
// 4. 점진적 개선
문제 4: 성능
# ❌ 느린 분석
clang-tidy -checks=* large_project/**/*.cpp
# ✅ 병렬 실행
find src -name "*.cpp" | xargs -P 8 -I {} clang-tidy {} -- -std=c++17
# ✅ 변경된 파일만
git diff --name-only --diff-filter=AM | grep '\.cpp$' | xargs clang-tidy
6. 정적 분석 도구 비교
| 도구 | 장점 | 단점 | 용도 |
|---|---|---|---|
| Clang-Tidy | 강력, 자동 수정, 확장 가능 | 느림, Clang 의존 | 주 도구 |
| Cppcheck | 빠름, 독립적, 간단 | 검출률 낮음 | 보조 도구 |
| PVS-Studio | 정확, 상세 리포트 | 상용 (비쌈) | 상용 프로젝트 |
| SonarQube | 웹 기반, 팀 협업, 다양한 언어 | 설정 복잡 | 대규모 팀 |
| Coverity | 정확, 엔터프라이즈 | 상용, 느림 | 엔터프라이즈 |
정리
핵심 요약
- 정적 분석: 코드 실행 없이 버그 탐지
- Clang-Tidy: 가장 강력한 도구
- Cppcheck: 빠르고 간단한 보조 도구
- CI/CD 통합: 자동화로 품질 보장
- 점진적 적용: 레거시 코드는 우선순위 설정
- 거짓 양성: 억제 주석으로 관리
정적 분석 워크플로우
코드 작성
↓
로컬에서 Clang-Tidy 실행
↓
문제 수정 또는 억제
↓
커밋
↓
CI/CD에서 자동 검사
↓
리포트 확인
↓
코드 리뷰
실전 팁
도구 선택:
- 주 도구: Clang-Tidy (강력, 자동 수정)
- 보조 도구: Cppcheck (빠름, 간단)
- 상용: PVS-Studio, Coverity (정확)
적용 전략:
- 새 프로젝트: 처음부터 적용
- 레거시: 점진적 적용 (새 코드부터)
- 우선순위: 버그 > 성능 > 스타일
성능 최적화:
- 병렬 실행 (
xargs -P) - 변경된 파일만 검사
- 필요한 검사만 활성화
다음 단계
- C++ Code Coverage
- C++ Profiling
- C++ Clang-Tidy & Cppcheck
관련 글
- C++ 정적 분석 도구 통합: Clang-Tidy와 Cppcheck로 코드 퀄리티 강제하기 [#41-1]
- C++ 정적 분석 도구 | Clang-Tidy·Cppcheck·SonarQube [#53-5]
- C++ 코드 커버리지 완벽 가이드 | gcov, lcov, Codecov 실전 활용
- C++ CLion 완벽 설정 가이드 | CMake·디버거·코드 분석·vcpkg·원격 개발 [#53-1]
- 배열과 리스트 | 코딩 테스트 필수 자료구조 완벽 정리