通过比较这两种模式,可以发现调试模式会添加调试符号(在许多编译器上通常使用-g选项),但同时会禁用大多数优化操作。
而在“发布”模式下,通常会开启各种优化操作。
那么,为什么存在这样的差异呢?
没有任何优化时,代码的执行流程是线性的。如果你在第5行并单步执行,你将会执行第6行。启用优化后,你可以获得指令重排、循环展开和各种优化。
例如:
void foo() {
1: int i;
2: for(i = 0; i < 2; )
3: i++;
4: return;
在这个例子中,如果没有进行优化,你可能需要逐行调试代码并命中第1、2、3、2、3、2、4行。
开启优化后,您可能会得到类似于2、3、3、4甚至只有4的执行路径!(毕竟该函数什么也不做...)
总之,启用优化调试代码可能会非常痛苦!特别是当您有大型函数时。
请注意,开启优化会改变代码!在某些环境(如安全关键系统)中,这是不可接受的,正在调试的代码必须是要发布的代码。在这种情况下,必须在开启优化的情况下进行调试。
虽然优化和非优化代码在“功能上”应该是等效的,在某些情况下,行为会发生变化。
以下是一个简单的例子:
int* ptr = 0xdeadbeef; // 指向内存映射I/O设备的地址
*ptr = 0; // 设置硬件设备
while(*ptr == 1) { // 循环直到硬件设备完成
// 做一些事情
}
关闭优化时,这很简单,您可以大致知道会发生什么。 但是,如果您打开优化,可能会发生以下几件事:
在所有这些情况下,行为都会发生显著的不同,很可能是错误的。
优化中的另一个问题是内联函数,因为您总是会逐个步骤地通过它们。
使用GCC,启用调试和优化后,如果您不知道该期望什么,您会认为代码行为异常,并且会多次重新执行相同语句-我有一些同事也曾遇到过这种情况。此外,带有优化的调试信息通常比它们本应具有的质量要差。
然而,在像Java这样的虚拟机托管的语言中,优化和调试可以共存 - 即使在调试过程中,JIT编译成本机代码仍将继续进行,只有经过调试的方法的代码会自动转换为非优化版本。
我想强调的是,除非使用的优化器有缺陷或者代码本身有缺陷并依赖于部分未定义的语义,否则优化不应改变代码的行为;后者在多线程编程或同时使用内联汇编时更为常见。
带有调试符号的代码较大,这可能意味着更多的缓存未命中,即速度较慢,这可能对服务器软件构成问题。
至少在Linux上(并且没有理由Windows应该有所不同),调试信息被打包在二进制文件的一个单独部分中,并且在正常执行期间不会加载。它们可以拆分成不同的文件用于调试。 此外,在某些编译器上(包括Gcc,我猜想也包括微软的C编译器),调试信息和优化可以同时启用。如果不能同时启用,则代码显然会变慢。
如果您在指令级别而不是源代码级别调试,则将未经优化的指令映射回源代码会更加容易。此外,编译器的优化器有时可能存在错误。
在微软的Windows部门中,所有发布的可执行文件都使用调试符号和完整的优化进行构建。这些符号存储在单独的PDB文件中,不会影响代码的性能。它们不会随产品一起发货,但大多数可在Microsoft Symbol Server上获得。
volatile int* ptr
怎么样?如果我理解正确,它将解决第2点和第3点,对吗?但是我不确定while
。 - Alexander Malakhov