在大多数C或C ++环境中,有“调试”模式和“释放”模式编译
看看两者之间的区别,您会发现调试模式添加了调试符号(通常是许多编译器上的-g选项),但它也会禁用大多数优化。
在“发布”模式下,您通常会启用各种优化
为什么不同?
答案 0 :(得分:28)
没有任何优化,代码流是线性的。如果您在第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; // some address to memory-mapped I/O device
*ptr = 0; // setup hardware device
while(*ptr == 1) { // loop until hardware device is done
// do something
}
关闭优化,这很简单,你知道会发生什么。 但是,如果启用优化,可能会发生以下几种情况:
在所有这些情况下,行为将大不相同,而且很可能是错误的。
答案 1 :(得分:4)
调试和发布之间的另一个重要区别是如何存储局部变量。从概念上讲,局部变量在函数堆栈帧中分配存储。编译器生成的符号文件告诉调试器堆栈帧中变量的偏移量,因此调试器可以向您显示。调试器会查看内存位置以执行此操作。
但是,这意味着每次更改局部变量时,该源代码行的生成代码都必须将值写回堆栈中的正确位置。由于内存开销,这非常低效。
在发布版本中,编译器可以将本地变量分配给寄存器以用于函数的一部分。在某些情况下,它可能根本不为它分配堆栈存储(机器越多,寄存器就越容易)。
但是,调试器不知道寄存器如何映射到代码中特定点的局部变量(我不知道任何包含此信息的符号格式),因此它无法准确显示给您因为它不知道去哪里寻找它。
另一个优化是函数内联。在优化的构建中,编译器可以将foo()的调用替换为foo的实际代码,因为函数足够小。但是,当你尝试在foo()上设置一个断点时,调试器想要知道foo()指令的地址,并且不再有一个简单的答案 - 可能有成千上万的foo副本( )代码字节遍布您的程序。调试版本将保证您可以在某处放置断点。
答案 2 :(得分:3)
优化代码是一个自动化过程,可在保留语义的同时提高代码的运行时性能。此过程可以删除不完整的中间结果以完成表达式或函数评估,但在调试时可能会对您感兴趣。类似地,优化可以改变明显的控制流,以便事情可能以与源代码中出现的顺序略有不同的顺序发生。这样做是为了跳过不必要或冗余的计算。这种代码的重新调整可能会破坏源代码行号和目标代码地址之间的映射,这使得调试器很难在编写代码时遵循控制流。
在未经优化的模式下进行调试,您可以在没有优化程序删除或重新排序的情况下查看您编写的所有内容。
如果您对程序运行正常感到满意,可以启用优化以提高性能。尽管优化器现在非常值得信赖,但建立一个高质量的测试套件仍然是一个好主意,以确保您的程序在优化和未优化模式下以相同的方式运行(从功能的角度来看,不考虑性能)。
答案 3 :(得分:2)
期望调试版本 - 调试!如果非空的非注释源代码的每一行都与某些机器代码指令匹配,那么设置断点,在监视变量时单步执行,堆栈跟踪以及在调试器(IDE或其他方面)中执行的所有操作都是有意义的。
大多数优化都会影响机器代码的顺序。循环展开就是一个很好的例子。常见的子表达式可以从循环中解除。启用优化后,即使是最简单的级别,您也可能尝试在机器代码级别不存在的行上设置断点。有时您无法监视本地变量,因为它保存在CPU寄存器中,或者甚至可能已经优化不存在!
答案 4 :(得分:1)
如果您在指令级别而不是源级别进行调试,那么您可以更轻松地将未经优化的指令映射回源。此外,编译器偶尔也会出现错误。
在Microsoft的Windows部门中,所有发布二进制文件都使用调试符号和完全优化构建。符号存储在单独的PDB文件中,不会影响代码的性能。它们不随产品一起提供,但大多数都可以在Microsoft Symbol Server处获得。
答案 5 :(得分:1)
优化的另一个问题是内联函数,也就是说你总是单步执行它们。
使用GCC,一起启用调试和优化,如果你不知道会发生什么,你会认为代码行为不端并多次重复执行相同的语句 - 这发生在我的几个同事身上。 此外,GCC给出的优化调试信息往往质量比实际情况要差。
但是,在像Java这样的虚拟机托管的语言中,优化和调试可以共存 - 即使在调试期间,JIT编译到本机代码也会继续,并且只有调试方法的代码才会透明地转换为未优化的版本。
我想强调优化不应该改变代码的行为,除非使用的优化器是错误的,或者代码本身是错误的并且依赖于部分未定义的语义;后者在多线程编程中或在使用内联汇编时更常见。
带有调试符号的代码更大,这可能意味着更多的缓存未命中,即更慢,这可能是服务器软件的问题。
至少在Linux上(并且没有理由说Windows应该有所不同)调试信息打包在二进制文件的单独部分中,并且在正常执行期间不会加载。它们可以拆分为不同的文件以用于调试。 此外,在一些编译器(包括Gcc,我猜也是微软的C编译器)上,调试信息和优化都可以同时启用。如果没有,显然代码会变慢。