我已经阅读了C语言中的《编程16位PIC单片机》,书中有这样的肯定:
但是,在项目的开发和调试阶段,最好禁用所有优化,因为它们可能会修改所分析代码的结构,并使单步执行和断点放置成为问题。
我承认我有些困惑。我不知道作者是因为C30评估期而这么说还是真的是一个好习惯。
我想知道您是否实际使用此做法,为什么?
我已经阅读了C语言中的《编程16位PIC单片机》,书中有这样的肯定:
但是,在项目的开发和调试阶段,最好禁用所有优化,因为它们可能会修改所分析代码的结构,并使单步执行和断点放置成为问题。
我承认我有些困惑。我不知道作者是因为C30评估期而这么说还是真的是一个好习惯。
我想知道您是否实际使用此做法,为什么?
Answers:
这在整个软件工程中都是很标准的-当您优化代码时,只要您不知道操作上的任何区别,编译器就可以按照自己的意愿重新安排很多事情。因此,例如,如果您在循环的每次迭代中初始化变量,而从不在循环内更改变量,则允许优化器将该初始化移出循环,以免浪费时间。
它也可能会意识到您计算了一个数字,然后您在覆盖之前不执行任何操作。在这种情况下,它可能会消除无用的计算。
优化的问题在于,您需要在一些代码上放置一个断点,优化器已将其移走或消除了。在这种情况下,调试器将无法执行您想要的操作(通常,它会将断点放置在附近)。因此,为使生成的代码更类似于您编写的代码,请在调试过程中关闭优化-这可确保确实存在要中断的代码。
您需要注意这一点,但是,根据您的代码,优化可能会破坏事情!通常,由运行正常的优化器破坏的代码实际上只是无法解决问题的错误代码,因此您通常想弄清楚为什么优化器会破坏它。
我已将这个问题发送给杰克·甘斯勒,这就是他的回答:
丹尼尔
我更喜欢使用发布的代码中的所有优化功能进行调试。美国宇航局说:“测试你的飞行,测试你的飞行。” 换句话说,不要进行测试,然后更改代码!
但是,有时必须关闭优化,以便调试器可以运行。我试图在我正在处理的模块中将其关闭。出于这个原因,我相信保持文件较小,说几百行代码左右。
祝一切顺利,杰克
取决于,并且所有工具(不仅是C30)都是如此。
优化通常以各种方式删除和/或重组代码。您的switch语句可能使用if / else构造重新实现,或者在某些情况下可能一起被删除。y = x * 16可能会替换为一系列左移,等等。尽管最后一种优化通常仍可逐步执行,但其主要是对获得ya的控制语句的重组。
这可能导致无法通过C代码逐步调试程序,因为您在C中定义的结构不再存在,它们已由编译器替换或重新排序为编译器认为会更快或更小的空间。这也可能导致无法从C列表中设置断点,因为断点的指令可能不再存在。例如,您可以尝试在if语句中设置一个断点,但是编译器可能已删除了该if。您可以尝试在while或for循环内设置断点,但编译器决定展开该循环,使其不再存在。
因此,如果您可以在不进行优化的情况下进行调试,则通常会更容易。您应该始终使用优化进行重新测试。这是您发现错过重要事项volatile
及其造成间歇性故障(或其他怪异现象)的唯一方法。
对于嵌入式开发,无论如何您都必须谨慎进行优化。特别是在对时间要求严格的代码部分中,例如一些中断。在这些情况下,您应该对汇编中的关键位进行编码,或者使用编译器指令来确保未对这些部分进行优化,因此您知道它们具有固定的执行时间或固定的最坏情况运行时间。
另一个难题可能是将代码适合于uC,您可能需要优化代码密度以使您的代码适合于芯片。这就是为什么通常一个好主意是从一个系列中最大的ROM容量uC开始,然后在代码被锁定后选择一个较小的ROM进行制造,这是一个好主意。
通常,我将使用计划发布的任何设置进行调试。如果要发布优化的代码,则可以使用优化的代码进行调试。如果要发布未优化的代码,则将使用未优化的代码进行调试。我这样做有两个原因。首先,优化器可以产生足够大的时序差异,从而导致最终产品的行为与未优化的代码不同。其次,尽管大多数都不错,但是编译器供应商确实会犯错误,并且优化的代码可能会与未优化的代码产生不同的结果。因此,无论我打算发布什么设置,我都希望获得尽可能多的测试时间。
话虽如此,优化器会使调试变得困难,如先前的答案所述。如果发现很难调试的特定代码部分,我将暂时关闭优化器,进行调试以使代码正常工作,然后重新打开优化器并再次进行测试。
我的正常策略是使用最终的优化程序进行开发(如果合适,最大尺寸或速度),但是如果我需要调试其他跟踪信息,则暂时关闭优化程序。这降低了由于更改优化级别而出现错误的风险。
典型的故障模式是,由于您没有在必要时将变量声明为易失性,因此不断增加的优化会导致以前看不见的错误浮出水面-这对于告诉编译器哪些事情不应该“优化”至关重要。
使用将要发布的任何形式,调试器和用于调试的编译会隐藏很多(很多)在编译发布之前看不到的错误。到那时,要找到这些错误要比进行调试要困难得多。到现在已有20多年了,我再也没有使用过gdb或其他类似调试器的东西,不需要监视变量或单步执行。每天数百到数千行。因此有可能,不要导致其他想法。
为调试而进行编译,然后为发行而进行后续编译,可能会花费两倍甚至两倍以上的精力。如果您陷入困境,并且必须使用调试器之类的工具,则需要进行编译以使调试器解决特定的问题,然后再恢复为正常操作。
其他问题也同样存在,例如优化器使代码更快,因此对于嵌入式而言,尤其是您使用编译器选项进行时序更改,并且可能会影响程序的功能,因此在整个阶段再次使用可交付的编译选项。编译器也是程序,有错误,优化器会犯错误,有些人对此不抱任何信念。如果是这种情况,那么不进行优化就可以编译,没错,只要一直这样做就可以了。我更喜欢的路径是进行编译以进行优化,然后,如果我怀疑编译器问题禁用了优化(如果解决了该问题),则该优化通常会来回进行,有时会检查汇编器输出以找出原因。
我总是使用-O0(gcc选项关闭优化功能)开发代码。当我感觉到我想开始让事情更趋于发布时,我将从-Os(针对大小进行优化)开始,因为通常可以在缓存中保留的代码越多越好,即使它不是超级超级优化的。
我发现gdb与-O0代码一起使用时效果更好,如果您必须进入程序集,则遵循起来要容易得多。在-O0和-Os之间切换还可以查看编译器对代码的作用。有时这是很有趣的教育,并且还可以发现编译器错误...那些使您烦恼的讨厌的事情,试图找出代码的问题所在!
如果确实需要,我将开始在-fdata-sections和-fcode-sections中添加--gc-sections,这使链接器可以删除实际上不使用的全部功能和数据段。您可以修补很多小东西,以尝试进一步缩小或加快速度,但总的来说,这些是我最终使用的唯一技巧,而任何较小或更快的东西我都会处理-组装。
是的,由于以下三个原因,在调试期间禁用优化一直是最佳实践,这已经有一段时间了:
许多人朝着这个方向走得更远,并且 在断言之前打开了。
反对在编译中启用优化的大多数论点归结为:
恕我直言,前两个是合法的,第三个不是很多。这通常意味着您有一些错误的代码,或者依赖于对语言/实现的不安全利用,或者作者可能只是对Undefined Undefined Behaviour的忠实粉丝。
“嵌入式学术界”博客对未定义的行为有一两句话要说,这篇文章是关于编译器如何利用它的:http : //blog.regehr.org/archives/761