深入理解编译器优化技术与调试

在现代软件开发中,编译器优化技术是提高程序性能和资源利用率的关键手段。然而,这些优化技术在调试过程中可能会带来一些挑战。本文将探讨链接时优化(Link-time optimization, LTO)技术在编译过程中的应用及其对调试的影响,特别是针对AVR微控制器和Arduino程序。

链接时优化(LTO)简介

链接时优化是一种编译器优化技术,它在最终链接生成二进制文件之前,考虑整个程序。LTO是相对较新的技术,大约十年前成为GCC工具链的一部分。与传统的只针对单个编译单元(即源文件)的优化技术不同,LTO可以跨所有链接的编译单元消除未使用的函数、方法和类成员。

LTO的一个缺点是这种优化可能需要很长时间。与传统的链接阶段仅解决地址问题(仅需几秒钟)不同,全局链接时优化可能需要几分钟。此外,由于跨编译单元的函数内联,可能会导致更大的堆栈帧,从而可能导致堆栈溢出。最后,由于删除代码部分和重新排列其他部分,调试可能会变得更加困难。

调试面向对象程序

自从ArduinoIDE 1.8.11版本(2020年初发布)以来,LTO默认启用,因为它通常可以显著降低对闪存内存的需求。然而,直到昨天,都没有注意到这一点,这是一个好兆头。

编写了一个小型面向对象程序,旨在作为正在开发的调试器的测试案例。下面是一个简化的版本:

class TwoDObject { public: int x, y; TwoDObject(int xini, int yini) { x = xini; y = yini; } void move(int xchange, int ychange) { x = x + xchange; y = y + ychange; } }; class Rectangle : public TwoDObject { public: int height, width; Rectangle(int xini, int yini, int heightini, int widthini) : TwoDObject(xini, yini) { height = heightini; width = widthini; } int area(void) { return (width * height); } }; Rectangle r(10, 11, 5, 8); void setup(void) { Serial.begin(9600); Serial.print(F("\nr position: ")); Serial.print(r.x); Serial.print(","); Serial.println(r.y); Serial.print(F("\narea: ")); Serial.print(r.area()); Serial.println(); Serial.println(F("\nMove r by +10, +10:")); r.move(10, 10); Serial.print(F("\nr position: ")); Serial.print(r.x); Serial.print(","); Serial.println(r.y); } void loop() { }

有一个基类TwoDObject和一个派生类Rectangle,其中包含一些成员变量和函数,以及最小的继承。没有什么特别花哨的。

禁用LTO进行调试

现在,使用ArduinoIDE或arduino-cli编译这个草图,并设置优化级别为调试友好(参见之前关于启用Arduino IDE进行调试的文章),即,指定优化选项-Og。在启动调试器并将二进制文件上传到Arduino板之后,可能想知道实例变量r的类型(使用调试器命令ptype)以及成员变量r.x的值。

在添加了-fno-lto选项到构建标志之后,即有效地禁用了LTO优化。使用arduino-cli时,可以通过添加--build-property选项来实现:

arduino-cli compile -b ... -e --build-property "build.extra_flags=-Og -fno-lto"

再次调用调试器,现在会得到一个更“现实”的画面:

(gdb) ptype r type = class Rectangle : public TwoDObject { public: int height; int width; Rectangle(int, int, int, int); int area(void); } (gdb) print r.x $1 = 0 (gdb) print r $2 = { = {x = 0, y = 12544}, height = 59, width = 0 }

这看起来是人们最初预期的样子。因此,关键的教训是,在调试时应该禁用flto选项。特别是当想要调试面向对象程序时。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485