C
Hintergrundgeschichte
Meine Frau hat eine Katze von der Familie geerbt. † Ich bin leider sehr allergisch gegen Tiere. Die Katze war weit hinter ihrer Blüte und hätte eingeschläfert werden müssen, bevor wir sie bekamen, aber sie konnte sich aufgrund ihres sentimentalen Werts nicht dazu bringen, sie loszuwerden. Ich habe einen Plan entwickelt, um mein Leiden zu beenden .
Wir machten einen längeren Urlaub, aber sie wollte nicht in der Tierarztpraxis an Bord gehen. Sie war besorgt darüber, dass sie krank wurde oder misshandelt wurde. Ich habe eine automatische Katzenfütterung entwickelt, damit wir sie zu Hause lassen können. Ich habe die Firmware des Mikrocontrollers in C geschrieben. Die Datei main
sah ähnlich aus wie der folgende Code.
Meine Frau ist jedoch auch Programmiererin und kannte meine Gefühle gegenüber der Katze. Deshalb bestand sie auf einer Codeüberprüfung, bevor sie zustimmte, sie unbeaufsichtigt zu Hause zu lassen. Sie hatte mehrere Bedenken, darunter:
main
hat keine standardkonforme Signatur (für eine gehostete Implementierung)
main
gibt keinen Wert zurück
tempTm
wird nicht initialisiert verwendet, da malloc
statt aufgerufen wurdecalloc
- Der Rückgabewert von
malloc
sollte nicht umgewandelt werden
- Die Zeit des Mikrocontrollers ist möglicherweise ungenau oder überschlägt sich (ähnlich wie bei den Problemen mit der Y2K- oder Unix-Zeit 2038).
- Die
elapsedTime
Variable verfügt möglicherweise nicht über einen ausreichenden Bereich
Es hat viel Überzeugungsarbeit gekostet, aber sie stimmte schließlich zu, dass dies aus verschiedenen Gründen keine Probleme darstellte (es hat nicht geschadet, dass wir bereits zu spät zu unserem Flug kamen). Da keine Zeit für Live-Tests war, genehmigte sie den Code und wir fuhren in den Urlaub. Als wir ein paar Wochen später zurückkehrten, war das Elend meiner Katze vorbei (obwohl ich jetzt viel mehr habe).
† Völlig fiktives Szenario, keine Sorge.
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;
}
}
}
Undefiniertes Verhalten:
Für diejenigen, die sich nicht die Mühe machen wollen, die UB selbst zu finden:
In diesem Code ist definitiv ein lokalspezifisches, nicht angegebenes und implementierungsspezifisches Verhalten enthalten, das jedoch ordnungsgemäß funktionieren sollte. Das Problem liegt in den folgenden Codezeilen:
struct tm * tempTm //...
//...
memcpy(&tempTm, localtime(&nowTime), sizeof(struct tm));
memcpy
Überschreibt den tempTM
Zeiger anstelle des Objekts, auf das er zeigt, und zerschmettert den Stapel. Dies überschreibt, neben anderen Dingen, elapsedTime
und loopIterationsSinceFeed
. Hier ist ein Beispiellauf, in dem ich die Werte ausgedruckt habe:
pre-smash : elapsedTime=1394210441 loopIterationsSinceFeed=1
post-smash : elapsedTime=65 loopIterationsSinceFeed=0
Wahrscheinlichkeit, die Katze zu töten:
- In Anbetracht der eingeschränkten Ausführungsumgebung und der Build-Kette tritt das undefinierte Verhalten immer auf.
- In ähnlicher Weise verhindert das undefinierte Verhalten immer, dass die Katzenfütterung wie beabsichtigt arbeitet (oder vielmehr, dass sie wie beabsichtigt "arbeitet").
- Wenn der Futterautomat nicht funktioniert, ist es sehr wahrscheinlich, dass die Katze stirbt. Dies ist keine Katze, die sich selbst verteidigen kann, und ich habe den Nachbarn nicht gebeten, nachzusehen.
Ich schätze, dass die Katze mit einer Wahrscheinlichkeit von 0,995 stirbt .