为什么C/C++程序经常在调试模式下关闭优化?

Ben*_*oit 12 c c++ compiler-construction

在大多数C或C++环境中,存在"调试"模式和"释放"模式编译.
看看两者之间的区别,您会发现调试模式添加了调试符号(通常是许多编译器上的-g选项),但它也会禁用大多数优化.
在"发布"模式下,您通常会启用各种优化.
为什么不同?

Ben*_*oit 28

没有任何优化,代码流是线性的.如果您在第5行并且单步执行,则跳到第6行.通过优化,您可以获得指令重新排序,循环展开和各种优化.
例如:


void foo() {
1:  int i;
2:  for(i = 0; i < 2; )
3:    i++;
4:  return;
Run Code Online (Sandbox Code Playgroud)

在这个例子中,没有优化,你可以单步执行代码并点击第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
    }

优化关闭,这很简单,你知道会发生什么.但是,如果您打开优化,可能会发生以下几种情况:

  • 编译器可能会优化while块(我们初始化为0,它永远不会是1)
  • 可以将指针访问移动到寄存器 - >无I/O更新,而不是访问存储器
  • 内存访问可能被缓存(不一定与编译器优化相关)

在所有这些情况下,行为将完全不同,很可能是错误的.


Rob*_*ker 6

调试和发布之间的另一个重要区别是局部变量的存储方式。从概念上讲,局部变量在函数堆栈帧中分配存储空间。编译器生成的符号文件告诉调试器变量在堆栈帧中的偏移量,以便调试器可以显示给你。调试器通过查看内存位置来执行此操作。

但是,这意味着每次更改局部变量时,该源代码行的生成代码都必须将值写回堆栈上的正确位置。由于内存开销,这非常低效。

在发布版本中,编译器可以将局部变量分配给函数的一部分的寄存器。在某些情况下,它可能根本不为其分配堆栈存储(机器拥有的寄存器越多,这样做就越容易)。

但是,调试器不知道寄存器如何映射到代码中特定点的局部变量(我不知道包含此信息的任何符号格式),因此它无法准确地向您显示它,因为它没有不知道去哪里找。

另一个优化是函数内联。在优化构建中,编译器可能会在任何使用它的地方用 foo 的实际代码替换对 foo() 的调用,因为该函数足够小。但是,当您尝试在 foo() 上设置断点时,调试器想要知道 foo() 指令的地址,对此不再有简单的答案——可能有数千个 foo() 副本) 代码字节分布在您的程序中。调试版本将保证您可以在某个地方放置断点。