C
Histoire
Ma femme a hérité d'un chat de la famille. † Malheureusement, je suis très allergique aux animaux. Le chat avait bien dépassé son apogée et aurait dû être euthanasié avant même que nous l'ayons eu, mais elle ne pouvait se résoudre à s'en débarrasser à cause de sa valeur sentimentale. J'éclos un plan pour mettre fin à ma ses souffrances.
Nous allions en vacances prolongées, mais elle ne voulait pas monter à bord du chat chez le vétérinaire. Elle craignait de contracter une maladie ou d'être maltraité. J'ai créé un chargeur automatique de chat afin que nous puissions le laisser à la maison. J'ai écrit le micrologiciel du microcontrôleur en C. Le fichier contenant main
ressemblait au code ci-dessous.
Cependant, ma femme est également programmeuse et connaissait mes sentiments pour le chat. Elle a donc insisté sur une révision du code avant d'accepter de le laisser à la maison sans surveillance. Elle avait plusieurs préoccupations, notamment:
main
n'a pas de signature conforme aux normes (pour une implémentation hébergée)
main
ne retourne pas de valeur
tempTm
est utilisé non initialisé car a malloc
été appelé au lieu decalloc
- la valeur de retour de
malloc
ne doit pas être exprimée
- le temps du microcontrôleur peut être inexact ou survoler (problème semblable à celui du problème Y2K ou Unix 2038)
- la
elapsedTime
variable peut ne pas avoir une plage suffisante
Cela a pris beaucoup de conviction, mais elle a finalement convenu que ces thèses ne posaient pas de problèmes pour diverses raisons (cela ne faisait pas de mal que nous soyons déjà en retard pour notre vol). Comme il n'y avait pas de temps pour les tests en direct, elle a approuvé le code et nous sommes partis en vacances. Lorsque nous sommes revenus quelques semaines plus tard, la souffrance de mon chat était terminée (bien qu'en conséquence, j'en ai maintenant beaucoup plus).
† Scénario entièrement fictif, pas de souci.
Code
#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;
}
}
}
Comportement non défini:
Pour ceux qui ne veulent pas chercher l’UB eux-mêmes:
Il y a définitivement un comportement spécifique à la localité, non spécifié et défini par l'implémentation dans ce code, mais cela devrait fonctionner correctement. Le problème se situe dans les lignes de code suivantes:
struct tm * tempTm //...
//...
memcpy(&tempTm, localtime(&nowTime), sizeof(struct tm));
memcpy
écrase le tempTM
pointeur au lieu de l’objet qu’il pointe, cassant la pile. Cela écrase, en plus d’autres choses, elapsedTime
et loopIterationsSinceFeed
. Voici un exemple d'exécution où j'ai imprimé les valeurs:
pre-smash : elapsedTime=1394210441 loopIterationsSinceFeed=1
post-smash : elapsedTime=65 loopIterationsSinceFeed=0
Probabilité de tuer le chat:
- Compte tenu de l'environnement d'exécution contraint et de la chaîne de construction, le comportement indéfini se produit toujours.
- De même, le comportement indéfini empêche toujours le chargeur de chat de fonctionner comme prévu (ou plutôt de lui permettre de "fonctionner" comme prévu).
- Si le chargeur ne fonctionne pas, il est extrêmement probable que le chat meure. Ce n'est pas un chat qui peut se débrouiller tout seul et j'ai omis de demander au voisin de le surveiller.
J’estime que le chat meurt avec une probabilité de 0,995 .