当闪光灯上的位置用完后,需要推挤不可食用的东西,牺牲一些需要的东西时,情况是否熟悉? 让我们尝试牺牲不必要的东西,它隐藏在相当意外的地方。
我想制作一个telnet服务器,以控制流行且便宜的WIZnet W5500模块上的各种设备。 所需的一切都是Arduino标准库的一部分,结果可在
此处找到。 但这与他无关。 让我真正感到惊讶的第一件事是,这种简单的功能代码占据了ATmega328P闪存的一半以上。 当然,以太网库中有很多代码,但并不是全部都使用了,编译器必须从组装的固件中丢弃未使用的代码。 检查是否是这样。
我们转到进行汇编的目录-可以在编译消息中看到其路径,然后执行objdump -t <elf firmware file>以获得字符表。 我们在其中看到了许多与以太网相关的功能,包括那些需求不明显的功能,例如,用于UDP的功能。 即,看起来好像没有发生不必要的功能的删除。 怎么了
答案似乎是出乎意料的-整个问题是从具有许多虚函数的基类继承了实现以太网的类。 当在代码的其他位置存在指向函数(或类方法)的链接时,编译器会认为已使用该函数。 但是,为了创建这样的链接,不必调用该函数。 保存她的地址就足够了。 即使我们没有明确进行此操作,C ++也会通过在虚拟函数表中放置指向函数的指针来为我们完成此操作。 即使我们从不使用此虚拟功能,它也会存在于固件中。 如果在基类中将一个函数定义为纯虚函数(不执行),那么即使根本不需要它,我们也只能执行它,因此会增加固件代码的大小。
我们验证了我们假设的正确性。 以github中的以太网库为例,
这里是为了避免触碰标准并进行修改。 我们删除继承,并仅通过方法创建虚拟函数。 如何以可逆的方式仔细地进行操作,请参见
此处 。 结果:代码大小减少了4460字节-超过原始大小的四分之一。
当然,继承和虚函数是有用的。 但是,创建带有纯虚函数的基类只是为了定义用于后续实现的接口并不总是合理的。 首先,您需要确保确实将这个接口与不同类型的对象一起使用,否则在基类(例如Print类)中实现的功能将对您有用。