-g 将 DWARF 调试信息直接嵌入目标文件或可执行文件,而非生成独立 .gdb 文件;需对所有源文件统一加 -g,避免链接时调试信息缺失,并注意 -g 与 -O 共存时的调试准确性下降问题。

-g 不是生成“.gdb”文件,而是把调试信息直接嵌入到可执行文件或目标文件里。不存在独立的 .gdb 符号文件这种东西——这是常见误解。GCC/G++ 的 -g 选项作用是让编译器在生成的 .o 或最终二进制中,内联写入 DWARF 格式的调试数据(不是单独文件),GDB 运行时直接从该文件读取。
为什么加了 -g 还看不到源码行号?
典型现象:启动 gdb ./a.out 后用 list 命令只显示“No symbol table is loaded”,或者断点打在函数名上失败,bt 只显示地址不显示函数名。
- 没对所有源文件都加
-g:比如g++ -g main.cpp -o a.out没问题,但若项目含utils.cpp却用g++ main.cpp utils.cpp -o a.out(漏了-g),则utils.o部分无调试信息,GDB 就无法回溯那部分调用 - 链接阶段覆盖了调试信息:使用
strip、objcopy --strip-debug或某些构建系统自动清理步骤会直接删掉-g写入的内容 - 用了
-g1却期望看到局部变量:-g1 只保留栈帧信息,不记录变量名和类型;要调试变量必须用-g(即-g2)或-g3 - 源文件路径变化:编译时的绝对路径被硬编码进调试信息,若移动源码目录后运行 GDB,
list可能报“not found”,可用directory命令添加搜索路径
g++ -g 和优化选项 -O 的真实关系
-g 和 -O 可以共存,但高优化等级会让调试体验严重退化——不是不能用,而是“能跑,但不准”。
-
-O0 -g:最安全组合。变量生命周期、代码执行顺序与源码基本一致,单步step和print可靠 -
-O1 -g:部分变量可能被优化掉(optimized out),循环可能被展开,但函数调用关系仍清晰 -
-O2/-O3 -g:常见后果包括:断点跳转错行、print var显示<optimized out></optimized>、step直接跳过整段逻辑、内联函数无法单独设断点 - 注意:
-g不会关闭优化,它只是“尽力保留映射”。想真正避免干扰,就得显式写-O0
怎么确认 -g 生效了?
别等 GDB 报错才验证,编译完立刻用命令检查:
- 查调试段是否存在:
readelf -S a.out | grep debug—— 应看到.debug_*段(如.debug_info、.debug_line) - 查符号表是否含调试符号:
nm -C a.out | grep " T "看函数,再加| grep " U "看未定义符号;但更关键的是objdump -g a.out | head -20能直接打印 DWARF 行号信息 - 快速 GDB 验证:
gdb -q ./a.out -ex "info sources" -ex quit—— 若输出列出所有源文件路径,说明-g成功注入
最容易被忽略的一点:CMake 项目里,set(CMAKE_CXX_FLAGS_DEBUG "-g -O0") 只影响 Debug 构建类型;如果用 cmake --build . 默认走 Release,那 -g 根本不会生效——务必确认构建类型或显式传 --config Debug。

















