调试符号与优化的有用性

Per*_*-lk 5 c c++ debugging compiler-optimization

我想了解在使用 in 或 进行编译时调试符号如何-g -O2仍然gcc有用clang

因为我理解,编译器首先添加调试符号,然后应用所有优化,同时不根据对代码执行的修改来修改或更新调试符号,因此在调试时可能会发生以下任何情况:优化已到位:

  • 断点可能会搞砸,从某种意义上说,我可能无法再在有意义的地方暂停:如果我说,暂停“这里”,这样的“这里”就不再存在或引用其他一些随机位置,等等我不再能够在我想要的地方停下来。

  • 尝试读取不再存在的某个变量的值可能会导致崩溃,因为根据符号,该变量仍然位于某个内存地址,但由于该变量不再存在,读取该内存地址可能会导致段错误。

  • 如果出现异常或像段错误这样的信号,调试器可以向我指出一行与实际故障点无关的代码。

即使我可以研究/理解生成的汇编输出,调试符号仍然毫无用处,因为它们已经相对于原始代码“冻结”了(在我的理解中),因此调试符号不再与当前的形状一致。代码。所以即使我能理解当前优化的代码,调试符号也不能帮助我调试代码,也不能尊重原始代码,更不能尊重最终的代码。

我的思路正确吗?已激活的调试符号如何与-g一起使用仍然有用-O2

我问这个问题是因为我读到使用组合-g -O2相对常见,这意味着它一定在某种程度上仍然有用,但考虑到您可以再信任调试符号,我不知道如何使用。

Sne*_*tel 7

您对优化代码中的调试符号为何无法按预期工作的理解不太正确。

生成调试符号以匹配最终的目标代码,即优化的代码。优化期间不需要“更新”,因为调试信息尚未保存。(当然,它已经以编译器内部数据结构的形式存在,但不需要与这些结构分开更新。)

相反,调试优化代码之所以奇怪是因为 (a) 代码行和 CPU 指令之间不再有简单且单调的映射,(b) 变量和寄存器/内存位置之间不再有简单的映射。

尤其:

断点可能会搞砸,从某种意义上说,我可能无法再在有意义的地方暂停:如果我说,暂停“这里”,这样的“这里”就不再存在或引用其他一些随机位置,等等我不再能够在我想要的地方停下来。

编译器不需要输出按照您指定的顺序执行操作的程序。假设您的程序有 snippet a = b*c + d; f = g - h;,并假设编译器决定最终程序应该在从 中减去 之前乘以b和,但在减法之后加上 。那么第一行代码是在第二行代码之前还是之后呢?好吧,两者都有!不过,编译器会尽力保留一半合理的映射。你最终会到达一个相当相关的地方chgd

尝试读取不再存在的某个变量的值可能会导致崩溃,因为根据符号,该变量仍然位于某个内存地址,但由于该变量不再存在,读取该内存地址可能会导致段错误。

嗯,不,不会的。即使调试器确实尝试读取无效位置,调试器也不会崩溃,但无论如何它可能都不会崩溃,因为该页面可能仍会映射到可执行文件。但并不要求变量始终位于同一“位置”(内存位置或寄存器)。不同的编译器和不同的编译器版本在跟踪变量当前位置方面做得更好或更差。但正如你所说,它在特定时刻可能不存在。

如果出现异常或像段错误这样的信号,调试器可以向我指出一行与实际故障点无关的代码。

即使对于未优化的代码,如果发生堆栈粉碎(通常在段错误之前发生),这也可能是正确的。否则,这实际上只是第一个问题的变体:“实际故障点”不是一行代码,而是一条指令。因此编译器必须决定指令与哪一行代码(如果有)最密切相关。

编译器输出合理且有用的调试信息以优化代码的能力各不相同。IMO Visual C++ 是迄今为止最好的,Clang 比 GCC 更好,但这实际上取决于优化的类型和编译器的版本。

底线:从优化代码的调试信息中获得的信息存在明显的缺陷。但正如有人曾经说过的那样“我知道这很糟糕,但这是城里唯一的游戏”。看到段错误StringMuncher::chew()可能表明也可能不表明该chew()函数实际上是罪魁祸首,但很可能确实如此,这比仅看到段错误位于地址 0x6AE002E0 更好还是更糟?

有多种技术可以用来帮助调试优化的代码,但这些技术超出了这个问题的范围,并且可能已经在另一个问题中进行了描述。