@[toc]

GCC 的 -O0-Og 与发布版调试说明

在这里插入图片描述

结论

  • -O0:基本关闭大多数优化,调试时更接近源码书写顺序。
  • -Og不是完全不优化,而是“为了调试体验而保留一部分优化”。
  • 因为 -Og 仍然会做一部分优化,所以它不能按 -O0 的方式理解;单步、变量位置、控制流都可能和 -O0 不同。
  • 如果发布版本使用 -Os,那么定位发布版问题时,通常更适合使用 -Os -g -gdwarf-2,而不是切回 -Og-O0
  • -g 的作用是生成调试信息
  • -gdwarf-2 的作用是强制调试信息使用 DWARF2 格式,常用于兼容较旧的 GDB、IDE 或嵌入式调试器。

-O0-Og 的核心区别

-O0

-O0 是 GCC 的默认优化级别。它会关闭大多数优化阶段,目标是让:

  • 编译结果尽量直接
  • 机器指令和源码的对应关系更直观
  • 单步调试更容易按源码顺序观察

因此,-O0 更适合这些场景:

  • 你要确认某一行到底有没有执行
  • 你要观察最原始的控制流
  • 你要尽量减少“编译器改写代码形态”带来的干扰

-O0 也有明显代价:

  • 代码通常更大、更慢
  • 时序行为更容易偏离发布版
  • 在 RTOS / 中断 / DMA 这类工程里,-O0 往往会放大调试失真

-Og

-Og 的定位不是“关闭优化”,而是:

  • 保留一部分不会明显破坏调试体验的优化
  • 提供比 -O0 更接近真实运行状态的代码
  • 在调试友好性和执行行为之间做折中

因此要特别强调:

-Og 有优化。
它不是“不优化模式”。
不能把它当成和 -O0 一样的“所见即所得”模式。


为什么 -Og 不能按 -O0 来理解

因为 -Og 仍然会启用一部分优化,所以调试时可能出现这些现象:

  • 某些变量不在你直觉中的位置
  • 某些中间变量被合并或不再单独保留
  • 某些语句的执行顺序和源码视觉顺序不完全一致
  • 某些断点位置和实际停下的位置略有偏差
  • 单步时控制流看起来“不像源码一行一行往下走”

这并不是调试器坏了,而是因为:

  • -Og 本身就不是“完全禁止优化”
  • 优化后的调试信息只能尽量映射源码,不能保证和 -O0 完全一致

所以:

  • 想要最强的源码直观性:用 -O0
  • 想要日常工程调试更平衡:用 -Og

为什么发布版调试更适合 -Os -g -gdwarf-2

如果你的发布版本是:

1
-Os

那么当你要定位“发布版才出现的问题”时,更推荐:

1
-Os -g -gdwarf-2

而不是:

1
-Og -g -gdwarf-2

更不是:

1
-O0 -g -gdwarf-2

原因是:

1. -Os -g -gdwarf-2 更接近真实发布行为

你要调的是发布版问题,就应尽量保持发布版的优化方式不变。
-Os 会影响:

  • 内联
  • 基本块布局
  • 寄存器分配
  • 代码重排
  • 尺寸与速度之间的权衡

如果你改成 -Og-O0,就等于把代码形态改了。
这样即使问题“消失了”,也不一定说明根因没了,可能只是被不同的优化结果掩盖了。

2. -g 不会取消 -Os

-g 的作用是生成调试信息,不会把优化关掉。
所以:

1
-Os -g

表示的是:

  • 仍按 -Os 优化生成目标代码
  • 同时尽可能保留给调试器使用的符号、行号、变量等信息

这正适合“调发布版行为”。

3. -gdwarf-2 常用于兼容嵌入式调试环境

现代 GCC 默认通常会生成较新的 DWARF 版本。
但不少嵌入式环境里,老版本 GDB、IDE 插件、ST-Link/J-Link 配套组件,对旧版 DWARF 的兼容性更稳。
因此很多嵌入式工程会显式加:

1
-gdwarf-2

它的主要意义不是“调得更清楚”,而是“调试器更容易稳定识别”。


-g 的作用

-g 的作用是:

  • 生成调试信息
  • 让 GDB、OpenOCD、IDE 调试器能够识别源码行号、函数、变量等信息
  • 支持断点、单步、回溯、查看局部变量

要注意:

  • -g 不是优化选项
  • -g 不会关闭 -O 优化
  • -g 可以和 -O0-Og-Os-O2-O3 一起使用

因此:

1
-Os -g

不是“变回调试版”,而是“发布优化代码 + 调试信息”。


-gdwarf-2 的作用

-gdwarf-2 的作用是:

  • 指定调试信息格式为 DWARF version 2
  • 约束 GCC 不去使用更新版本的 DWARF 表达方式
  • 在一些较旧的调试器或嵌入式 IDE 中获得更稳的兼容性

它不直接改变程序逻辑,也不直接改变优化行为。
它改变的是“调试信息采用哪一种格式编码”。

也就是说:

  • -g:生成调试信息
  • -gdwarf-2:把调试信息格式固定成 DWARF2

两者通常会一起出现:

1
-g -gdwarf-2

推荐的工程配置

开发调试版

适合日常开发、单步、查普通问题:

1
-Og -g -gdwarf-2

特点:

  • -O0 更接近真实执行状态
  • 调试体验通常比纯 -O0 更平衡
  • 适合 RTOS / 中断 / DMA / 时序相关工程

发布版

适合最终出货:

1
-Os

特点:

  • 面向代码尺寸优化
  • 没有调试信息
  • 体积更小

发布可调试版

适合复现和定位发布版问题:

1
-Os -g -gdwarf-2

特点:

  • 保持发布版优化方式
  • 增加调试信息
  • 更适合查“只在发布版出现”的问题

临时强可视调试版

只在你非常需要源码级直观映射时使用:

1
-O0 -g -gdwarf-2

特点:

  • 最接近源码书写顺序
  • 但和真实发布行为差异最大
  • 不适合拿来替代发布问题定位

选项使用建议

建议 1

日常调试优先:

1
-Og -g -gdwarf-2

建议 2

发布问题定位优先:

1
-Os -g -gdwarf-2

建议 3

只有在你非常需要“最直观单步”时,才切到:

1
-O0 -g -gdwarf-2

一句话总结

  • -O0:尽量不优化,便于按源码直观看执行
  • -Og:为了调试而保留部分优化,不是完全不优化
  • -Os -g -gdwarf-2:更适合调发布版问题
  • -g:生成调试信息
  • -gdwarf-2:指定调试信息采用 DWARF2 格式,偏兼容性用途