¿Es familiar la situación cuando el lugar en el flash se ha agotado y necesita empujar lo no comestible, sacrificando algo que necesita? Probemos en cambio sacrificar innecesariamente, se esconde en lugares bastante inesperados.
Quería hacer un servidor telnet para controlar varios equipos en el popular y económico módulo WIZnet W5500. Todo lo que se necesita para esto es parte de la biblioteca estándar de Arduino, el resultado se puede encontrar
aquí . Pero esto no se trata de él. Lo primero que realmente me sorprendió fue que este código de funcionalidad simple ocupaba más de la mitad del flash ATmega328P. Por supuesto, hay una gran cantidad de código en la biblioteca de Ethernet, pero no se usa todo, el compilador debe desechar el código no utilizado del firmware ensamblado. Comprueba si esto es así.
Vamos al directorio donde se lleva a cabo el ensamblado: la ruta hacia él se puede ver en los mensajes de compilación y objdump -t <elf firmware file> para obtener la tabla de caracteres. Vemos en él muchas funciones relacionadas con Ethernet, incluidas aquellas cuya necesidad no es obvia, por ejemplo, funciones para trabajar con UDP. Es decir, parece que no se eliminaron funciones innecesarias. Cual es el problema
La respuesta puede parecer inesperada: todo es la herencia de clases que implementan Ethernet desde clases base con muchas funciones virtuales. El compilador considera que una función (o método de clase) se usa cuando hay enlaces a ella en otros lugares del código. Pero para crear dicho enlace, no es necesario llamar a la función. Es suficiente para guardar su dirección. Incluso si no lo hacemos explícitamente, C ++ lo hace por nosotros colocando un puntero a una función en la tabla de funciones virtuales. Incluso si nunca usamos esta función virtual, estará presente en el firmware. Si una función se define en la clase base como puramente virtual (sin implementación), entonces no tenemos más opciones que implementarla, incluso si no la necesitamos, aumentando así el tamaño del código de firmware.
Verificamos la exactitud de nuestra hipótesis. Tome la biblioteca Ethernet del github, por ejemplo
aquí , para no tocar el estándar y modifíquelo. Eliminamos la herencia y hacemos funciones virtuales simplemente por métodos. Cómo hacerlo con cuidado, de forma reversible, puedes ver
aquí . Resultado: el tamaño del código disminuyó en 4460 bytes, más de una cuarta parte del tamaño original.
Por supuesto, la herencia y las funciones virtuales son útiles. Sin embargo, crear una clase base con funciones virtuales puras solo para definir una interfaz para implementaciones posteriores no siempre está justificado. Primero debe asegurarse de que realmente usará esta interfaz con objetos de diferentes tipos, o la funcionalidad implementada en la clase base (como en la clase Print) le será útil.