C
backstory
Mia moglie ha ereditato un gatto dalla famiglia. † Sfortunatamente, sono molto allergico agli animali. Il gatto era ben oltre il suo apice e avrebbe dovuto essere eutanizzato anche prima che lo ottenessimo, ma non riuscì a liberarsene a causa del suo valore sentimentale. Ho escogitato un piano per porre fine alla mia sofferenza.
Stavamo andando in vacanza prolungata, ma lei non voleva salire a bordo del gatto nell'ufficio del veterinario. Era preoccupata per la malattia contratta o maltrattata. Ho creato un alimentatore automatico per gatti in modo da poterlo lasciare a casa. Ho scritto il firmware del microcontrollore in C. Il file contenente main
sembrava simile al codice seguente.
Tuttavia, anche mia moglie è programmatrice e conosceva i miei sentimenti nei confronti del gatto, quindi ha insistito per una revisione del codice prima di accettare di lasciarlo a casa incustodito. Aveva diverse preoccupazioni, tra cui:
main
non ha una firma conforme agli standard (per un'implementazione ospitata)
main
non restituisce un valore
tempTm
viene utilizzato non inizializzato poiché è malloc
stato chiamato anzichécalloc
- il valore restituito di
malloc
non deve essere cast
- il tempo del microcontrollore potrebbe essere impreciso o capovolto (simile ai problemi Y2K o Unix time 2038)
- la
elapsedTime
variabile potrebbe non avere un intervallo sufficiente
Ci è voluto molto convincente, ma alla fine ha concordato sul fatto che queste non erano problemi per vari motivi (non ha fatto male che eravamo già in ritardo per il nostro volo). Dato che non c'era tempo per i test dal vivo, ha approvato il codice e siamo andati in vacanza. Quando siamo tornati poche settimane dopo, la mia sofferenza del gatto era finita (anche se di conseguenza ora ne ho molti di più).
† Scenario interamente fittizio, nessuna preoccupazione.
Codice
#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;
}
}
}
Comportamento indefinito:
Per coloro che non vogliono preoccuparsi di trovare l'UB stesso:
In questo codice c'è sicuramente un comportamento specifico locale, non specificato e definito dall'implementazione, ma tutto dovrebbe funzionare correttamente. Il problema è nelle seguenti righe di codice:
struct tm * tempTm //...
//...
memcpy(&tempTm, localtime(&nowTime), sizeof(struct tm));
memcpy
sovrascrive il tempTM
puntatore invece dell'oggetto a cui punta, distruggendo lo stack. Questo sovrascrive, oltre ad altre cose, elapsedTime
e loopIterationsSinceFeed
. Ecco un esempio in cui ho stampato i valori:
pre-smash : elapsedTime=1394210441 loopIterationsSinceFeed=1
post-smash : elapsedTime=65 loopIterationsSinceFeed=0
Probabilità di uccidere il gatto:
- Dato l'ambiente di esecuzione vincolato e la catena di costruzione, si verifica sempre un comportamento indefinito.
- Allo stesso modo, il comportamento indefinito impedisce sempre all'alimentatore di gatti di funzionare come previsto (o meglio, gli consente di "funzionare" come previsto).
- Se l'alimentatore non funziona, è molto probabile che il gatto muoia. Questo non è un gatto che può badare a se stesso e non sono riuscito a chiedere al vicino di guardarlo.
Stimo che il gatto muore con probabilità 0.995 .