C
Trasfondo
Mi esposa heredó un gato de la familia. † Desafortunadamente, soy muy alérgico a los animales. El gato ya había pasado su mejor momento y debería haber sido sacrificado incluso antes de que lo obtuviéramos, pero no pudo deshacerse de él debido a su valor sentimental. Elaboré un plan para poner fin a mi sufrimiento.
Nos íbamos de vacaciones, pero ella no quería subir al gato en la oficina del veterinario. Le preocupaba que contrajera enfermedades o fuera maltratada. Creé un alimentador automático de gatos para que pudiéramos dejarlo en casa. Escribí el firmware del microcontrolador en C. El archivo que contenía se main
parecía al código siguiente.
Sin embargo, mi esposa también es programadora y conocía mis sentimientos hacia el gato, por lo que insistió en una revisión del código antes de aceptar dejarlo en casa sin supervisión. Ella tenía varias preocupaciones, incluyendo:
main
no tiene una firma que cumpla con los estándares (para una implementación alojada)
main
no devuelve un valor
tempTm
se usa sin inicializar ya que malloc
se llamó en lugar decalloc
- el valor de retorno de
malloc
no se debe emitir
- El tiempo del microcontrolador puede ser impreciso o no se puede transferir (similar a los problemas de Y2K o Unix time 2038)
- la
elapsedTime
variable puede no tener suficiente rango
Le tomó mucho tiempo convencer, pero finalmente estuvo de acuerdo en que las tesis no eran problemas por varias razones (no estaba de más que ya llegáramos tarde a nuestro vuelo). Como no había tiempo para las pruebas en vivo, ella aprobó el código y nos fuimos de vacaciones. Cuando regresamos unas semanas más tarde, mi miseria del gato había terminado (aunque como resultado ahora tengo mucho más).
† Escenario completamente ficticio, no se preocupe.
Código
#include <time.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
//#include "feedcat.h"
// contains extern void FeedCat(struct tm *);
// implemented in feedcat.c
// stub included here for demonstration only
#include <stdio.h>
// passed by pointer to avoid putting large structure on stack (which is very limited)
void FeedCat(struct tm *amPm)
{
if(amPm->tm_hour >= 12)
printf("Feeding cat dinner portion\n");
else
printf("Feeding cat breakfast portion\n");
}
// fallback value calculated based on MCU clock rate and average CPI
const uintmax_t FALLBACK_COUNTER_LIMIT = UINTMAX_MAX;
int main (void (*irqVector)(void))
{
// small stack variables
// seconds since last feed
int elapsedTime = 0;
// fallback fail-safe counter
uintmax_t loopIterationsSinceFeed = 0;
// last time cat was fed
time_t lastFeedingTime;
// current time
time_t nowTime;
// large struct on the heap
// stores converted calendar time to help determine how much food to
// dispense (morning vs. evening)
struct tm * tempTm = (struct tm *)malloc(sizeof(struct tm));
// assume the cat hasn't been fed for a long time (in case, for instance,
// the feeder lost power), so make sure it's fed the first time through
lastFeedingTime = (size_t)(-1);
while(1)
{
// increment fallback counter to protect in case of time loss
// or other anomaly
loopIterationsSinceFeed++;
// get current time, write into to nowTime
time(&nowTime);
// calculate time since last feeding
elapsedTime = (int)difftime(nowTime, lastFeedingTime);
// get calendar time, write into tempTm since localtime uses an
// internal static variable
memcpy(&tempTm, localtime(&nowTime), sizeof(struct tm));
// feed the cat if 12 hours have elapsed or if our fallback
// counter reaches the limit
if( elapsedTime >= 12*60*60 ||
loopIterationsSinceFeed >= FALLBACK_COUNTER_LIMIT)
{
// dispense food
FeedCat(tempTm);
// update last feeding time
time(&lastFeedingTime);
// reset fallback counter
loopIterationsSinceFeed = 0;
}
}
}
Comportamiento indefinido:
Para aquellos que no quieren molestarse en encontrar la UB ellos mismos:
Definitivamente hay un comportamiento local específico, no especificado y definido por la implementación en este código, pero todo debería funcionar correctamente. El problema está en las siguientes líneas de código:
struct tm * tempTm //...
//...
memcpy(&tempTm, localtime(&nowTime), sizeof(struct tm));
memcpy
sobrescribe el tempTM
puntero en lugar del objeto al que apunta, rompiendo la pila. Esto sobrescribe, además de otras cosas, elapsedTime
y loopIterationsSinceFeed
. Aquí hay un ejemplo de ejecución donde imprimí los valores:
pre-smash : elapsedTime=1394210441 loopIterationsSinceFeed=1
post-smash : elapsedTime=65 loopIterationsSinceFeed=0
Probabilidad de matar al gato:
- Dado el entorno de ejecución restringido y la cadena de compilación, siempre se produce el comportamiento indefinido.
- Del mismo modo, el comportamiento indefinido siempre evita que el comedero para gatos funcione como se esperaba (o más bien, le permite "trabajar" como se pretende).
- Si el alimentador no funciona, es extremadamente probable que el gato muera. Este no es un gato que pueda valerse por sí mismo, y no pude pedirle al vecino que lo mirara.
Estimo que el gato muere con probabilidad 0.995 .