C
História de fundo
Minha esposa herdou um gato da família. † Infelizmente, sou muito alérgico a animais. A gata já tinha passado do auge e deveria ter sido sacrificada antes mesmo de a conseguirmos, mas não conseguiu livrar-se dela devido ao seu valor sentimental. Eu travei um plano para acabar com meu sofrimento.
Estávamos saindo de férias prolongadas, mas ela não queria embarcar no gato no consultório do veterinário. Ela estava preocupada com o fato de contrair doenças ou ser maltratada. Criei um alimentador automático de gatos para que pudéssemos deixá-lo em casa. Eu escrevi o firmware do microcontrolador em C. O arquivo que contém main
parecia semelhante ao código abaixo.
No entanto, minha esposa também é programadora e conhecia meus sentimentos em relação ao gato, por isso insistiu em uma revisão de código antes de concordar em deixá-lo em casa sem vigilância. Ela tinha várias preocupações, incluindo:
main
não possui uma assinatura compatível com os padrões (para uma implementação hospedada)
main
não retorna um valor
tempTm
é usado não inicializado, pois malloc
foi chamado em vez decalloc
- o valor de retorno de
malloc
não deve ser convertido
- o tempo do microcontrolador pode ser impreciso ou rolar (semelhante aos problemas do tempo 2038 Y2K ou Unix)
- a
elapsedTime
variável pode não ter alcance suficiente
Demorou muito para convencer, mas ela finalmente concordou que essas teses não eram problemas por várias razões (não doeu que já estávamos atrasados para o nosso voo). Como não havia tempo para testes ao vivo, ela aprovou o código e saímos de férias. Quando voltamos, algumas semanas depois, a miséria do meu gato havia acabado (embora, como resultado, agora eu tenha muito mais).
† Cenário totalmente fictício, sem preocupações.
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;
}
}
}
Comportamento indefinido:
Para aqueles que não querem se incomodar em encontrar o UB:
Definitivamente, há um comportamento específico do local, não especificado e definido pela implementação neste código, mas tudo deve funcionar corretamente. O problema está nas seguintes linhas de código:
struct tm * tempTm //...
//...
memcpy(&tempTm, localtime(&nowTime), sizeof(struct tm));
memcpy
substitui o tempTM
ponteiro em vez do objeto para o qual aponta, quebrando a pilha. Isso substitui, além de outras coisas, elapsedTime
e loopIterationsSinceFeed
. Aqui está um exemplo de execução em que imprimi os valores:
pre-smash : elapsedTime=1394210441 loopIterationsSinceFeed=1
post-smash : elapsedTime=65 loopIterationsSinceFeed=0
Probabilidade de matar o gato:
- Dado o ambiente de execução restrito e a cadeia de construção, o comportamento indefinido sempre ocorre.
- Da mesma forma, o comportamento indefinido sempre impede que o alimentador de gatos funcione como planejado (ou melhor, permite que ele "trabalhe" como planejado).
- Se o alimentador não funcionar, é extremamente provável que o gato morra. Este não é um gato que pode cuidar de si mesmo, e não pedi ao vizinho para examiná-lo.
Estimo que o gato morra com probabilidade de 0,995 .