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 对照
| 选项 | Debug | Release |
|---|---|---|
| 优化 | -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 |
| 链接库版本不符 | 运行时找到旧 so | ldd 检查,设 RPATH 或 LD_LIBRARY_PATH |
| Debug 正常 Release 崩溃 | UB 被优化暴露 | 用 ASan/UBSan 在 -O2 下复现 |
八、CI 里的最小质量门禁
- 全量编译(Debug + Release 两种);
-Wall -Wextra无新增警告;- ASan/UBSan 跑一遍单元测试;
- clang-tidy 关键检查项通过;
- 保留构建产物符号文件,便于线上崩溃分析。
九、日常习惯
- 崩了先看 coredump 与
bt,不要盲改代码; - 怀疑内存问题时直接上 ASan,比读代码快十倍;
- 怀疑 UB 时用 UBSan 在
-O2下跑(-O0常常不复现); - 怀疑性能时先
perf record,别猜; - 每次环境升级都更新这份配置,工具链是资产。
工具的意义不是替代思考,而是让思考有落点。