Hoy en día, a menudo pensamos en cómo la pila de un hilo le ayuda a tener la propiedad de los datos en lugar de compartirlos con otros hilos, pero en el mundo embebido sin sistema operativo, donde la arquitectura del software es un súper bucle, una pila puede parecer o incluso ser una complicación innecesaria, pero la mayoría de las veces ayuda más de lo que perjudica.
El superbucle de los programas embebidos suele consistir en una serie de llamadas a funciones dentro de un bucle "forever". Puede haber algún tipo de evento que cada función llegue a ver en cada llamada, el bucle entero puede bloquearse esperando un evento, o varias otras variaciones, pero siguen un patrón. (En arduino el súper bucle llama a la función del bucle una vez por ciclo a través del bucle real) Un súper bucle puede llamar a múltiples funciones (que representan diferentes módulos, como una interfaz de comunicación, la monitorización de la fuente de energía, la medición de algún sistema, el almacenamiento de datos en un almacenamiento no volátil, decirle a una mano robótica que frene su movimiento de agarre porque sus dedos se están acercando a la superficie que está agarrando, ). Cada una de estas funciones puede tener algún estado persistente que podría residir en la memoria global / ser declarado como variables globales (o podría hacerse de una manera más referenciada a objetos), pero es muy probable que al menos a veces una función del módulo necesite algunos datos temporales que sólo necesitan ser utilizados durante la duración de una sola llamada a esa función. Podrías declarar esos datos como globales y decirle a todos los demás que no los usen, pero si el espacio de la RAM es escaso, entonces podría no haber suficiente espacio para ello. Podrías usar la asignación de heap y liberarla cuando hayas terminado, pero eso introduce complicaciones (olvidarse de liberar en todas las rutas de código, doble liberación, fragmentación de heap, manejo de fallos de asignación de memoria, ).
Utilizando una pila cada módulo consigue reutilizar el mismo espacio de memoria. Es una superposición dinámica que funciona automáticamente para los casos en que la memoria sólo se necesita durante la duración de una sola llamada. Esto también hace que los casos que necesitan más memoria persistente sean más obvios, y puedes dedicar más esfuerzo a diseñar y planificar para ellos.
En una cpu con caché de datos también te beneficias de que la memoria de la pila esté casi siempre en la caché porque se accede a ella muy a menudo.
He programado varios sistemas en ensamblador, y uno de ellos no tenía pila de hardware. Tiene 16 registros, lo que normalmente era suficiente, pero también era muy común necesitar almacenar 1 palabra más por un momento y tener que gastar enormes cantidades de esfuerzo buscando un registro que estuviera libre (para esa parte del código) y luego probar que de hecho estaba libre. Hubiera sido mucho más fácil y seguro poder simplemente pulsar, hacer mis cosas, y salir.
Las pilas son tan útiles que al menos algunos 8051 tienen un espacio de memoria separado que es sólo la memoria de la pila. Algunos compiladores introducen una segunda pila de datos cuando compilan para AVR. Y Atmel (ahora parte de Microchip) hizo que los registros de algunos de sus microcontroladores estuvieran mapeados en la memoria, por lo que en algunos de sus sistemas más pequeños los registros podían usarse como pila.
El dolor de hacer los offsets de los punteros de la pila se ve muy aliviado por los compiladores del lenguaje y los depuradores conscientes del lenguaje. Es muy molesto cuando no se tiene acceso a ellos, pero incluso entonces prefiero tener una pila que no tenerla.
Recientemente estuve diseñando un sencillo programa embebido de bajo nivel que dormía el 99,9999% del tiempo y respondía a los eventos en las interrupciones, y para ello consideré ir (casi) sin pilas. Los ISRs no podían ser interrumpidos por sí mismos, cada uno de los módulos podía poseer algunos registros y RAM, y nadie, aparte del código de inicialización, necesitaba más que unos pocos bytes de datos.