跳到主要内容

C++ 项目构建与调试工具链

代码能跑只是开始。真正决定维护效率的是:能不能快速构建、能不能精确定位崩溃。本文给出一套可直接照抄的最小可用配置。

一、编译器常用选项​

g++ 与 clang++ 的选项基本通用,下面以 g++ 为例。

常用命令​

# 编译 + 链接
g++ -std=c++14 -O2 -Wall -Wextra -o app main.cpp util.cpp -lpthread

# 只编译
g++ -std=c++14 -c util.cpp -o util.o

# 预处理(排查宏与 include 问题)
g++ -E main.cpp -o main.i

# 生成汇编
g++ -S -O2 main.cpp -o main.s

# 查看符号表
nm -C app | grep foo

# 查看依赖库
ldd app

Debug 与 Release 对照​

选项DebugRelease
优化-O0-O2
调试信息-g3-g
断言保留-DNDEBUG
检查-fsanitize=address,undefined关闭
链接默认-flto

二、最小 CMake 配置​

cmake_minimum_required(VERSION 3.16)
project(demo CXX)

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) # 用 -std=c++14 而非 -std=gnu++14

if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

add_executable(app
src/main.cpp
src/util.cpp
)
target_include_directories(app PRIVATE include)
target_compile_options(app PRIVATE -Wall -Wextra)
target_link_libraries(app PRIVATE Threads::Threads)

# 调试配置
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
if (ENABLE_ASAN)
target_compile_options(app PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
target_link_options(app PRIVATE -fsanitize=address,undefined)
endif()

构建:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j

不要在 CMakeLists.txt 里硬编码 -O2 等标志,用 CMAKE_BUILD_TYPE 控制,否则 Debug/Release 切换会失效。

三、编译数据库与静态检查​

cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ln -sf build/compile_commands.json .

# clang-tidy
clang-tidy -p build src/main.cpp

# cppcheck(老项目友好,无需编译数据库)
cppcheck --enable=warning,performance,portability --std=c++14 src/

clang-tidy 值得长期开启的检查项:

  • bugprone-*:真实缺陷;
  • modernize-use-nullptr、modernize-use-override、modernize-use-auto:老代码现代化;
  • performance-*:无谓拷贝;
  • cppcoreguidelines-*:核心准则(可能噪声较多,按需筛选)。

四、gdb 常用命令​

g++ -g -O0 app.cpp -o app
gdb ./app
命令作用
run / r运行
bt调用栈
frame N / f N切到第 N 帧
p expr打印表达式
p *ptr解引用
info locals局部变量
info threads / thread N线程切换
break file.cpp:42断点
catch throw停在异常抛出点
watch var变量被写时停下
x/16xb ptr查看 16 字节内存
disassemble反汇编当前函数
call func()调用函数(有风险)

分析 coredump​

ulimit -c unlimited
./app # 崩溃后生成 core
gdb ./app core
(gdb) bt full # 完整栈 + 局部变量

生产环境务必保留符号文件,否则栈全是地址。

五、Sanitizer 三件套​

# AddressSanitizer:越界、UAF、double free、泄漏
g++ -g -fsanitize=address -fno-omit-frame-pointer app.cpp -o app

# UndefinedBehaviorSanitizer:有符号溢出、空指针解引用、对齐错误
g++ -g -fsanitize=undefined app.cpp -o app

# ThreadSanitizer:数据竞争
g++ -g -fsanitize=thread app.cpp -o app

注意:ASan 与 TSan 不能同时开启。CI 里建议按构建任务分开跑。

六、内存与性能分析​

# 内存泄漏(valgrind 更细,但慢)
valgrind --leak-check=full ./app

# 堆分配统计
valgrind --tool=massif ./app

# CPU 采样
perf record -g ./app && perf report

# 缓存未命中
perf stat -e cache-misses ./app

七、构建排错速查​

现象常见原因处理
undefined reference库未链接 / 顺序错 / 缺 extern "C"调整 target_link_libraries 顺序,补声明
multiple definition头文件定义非 inline 符号改 inline/static,或移到源文件
头文件循环包含互相 include前置声明 + 头文件守卫 #pragma once
改动未生效增量构建缓存cmake --build build --clean-first
链接库版本不符运行时找到旧 soldd 检查,设 RPATH 或 LD_LIBRARY_PATH
Debug 正常 Release 崩溃UB 被优化暴露用 ASan/UBSan 在 -O2 下复现

八、CI 里的最小质量门禁​

  1. 全量编译(Debug + Release 两种);
  2. -Wall -Wextra 无新增警告;
  3. ASan/UBSan 跑一遍单元测试;
  4. clang-tidy 关键检查项通过;
  5. 保留构建产物符号文件,便于线上崩溃分析。

九、日常习惯​

  • 崩了先看 coredump 与 bt,不要盲改代码;
  • 怀疑内存问题时直接上 ASan,比读代码快十倍;
  • 怀疑 UB 时用 UBSan 在 -O2 下跑(-O0 常常不复现);
  • 怀疑性能时先 perf record,别猜;
  • 每次环境升级都更新这份配置,工具链是资产。

工具的意义不是替代思考,而是让思考有落点。