С
Предыстория
Моя жена унаследовала кошку от семьи. † К сожалению, у меня очень аллергия на животных. Кошка прошла далеко от своего расцвета и должна была быть умерщвлена еще до того, как мы ее получили, но она не могла заставить себя избавиться от нее из-за ее сентиментальной ценности. Я разработал план, чтобы положить конец моим страданиям.
Мы собирались в длительный отпуск, но она не хотела садиться на кошку в офисе ветеринара. Она была обеспокоена тем, что она заболела или подверглась жестокому обращению. Я создал автоматическую кормушку для кошек, чтобы мы могли оставить ее дома. Я написал прошивку микроконтроллера на C. Файл, содержащийся, main
выглядел примерно так, как показано ниже.
Однако моя жена также программист и знала, что я чувствую к кошке, поэтому она настояла на проверке кода, прежде чем оставить ее дома без присмотра. У нее было несколько проблем, в том числе:
main
не имеет подписи, соответствующей стандартам (для размещенной реализации)
main
не возвращает значение
tempTm
используется неинициализированным, так как malloc
был вызван вместоcalloc
- возвращаемое значение
malloc
не должно быть приведено
- время микроконтроллера может быть неточным или переворачиваться (похоже на проблемы с Y2K или Unix time 2038)
elapsedTime
переменная не может иметь достаточный диапазон
Потребовалось много убеждений, но она, наконец, согласилась, что эти проблемы не были проблемами по разным причинам (не повредило, что мы уже опоздали на наш рейс). Поскольку времени для тестирования в прямом эфире не было, она утвердила код, и мы уехали в отпуск. Когда мы вернулись через несколько недель, мои кошачьи страдания закончились (хотя в результате у меня теперь намного больше).
† Полностью вымышленный сценарий, не беспокойтесь.
Код
#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;
}
}
}
Неопределенное поведение:
Для тех, кто не хочет искать UB самостоятельно:
В этом коде определенно определено локальное, неопределенное и определяемое реализацией поведение, но все должно работать правильно. Проблема заключается в следующих строках кода:
struct tm * tempTm //...
//...
memcpy(&tempTm, localtime(&nowTime), sizeof(struct tm));
memcpy
перезаписывает tempTM
указатель вместо объекта, на который он указывает, разбивая стек. Это переписывает, помимо прочего, elapsedTime
и loopIterationsSinceFeed
. Вот пример запуска, где я распечатал значения:
pre-smash : elapsedTime=1394210441 loopIterationsSinceFeed=1
post-smash : elapsedTime=65 loopIterationsSinceFeed=0
Вероятность убийства кота:
- Учитывая ограниченную среду выполнения и цепочку сборки, всегда возникает неопределенное поведение.
- Точно так же неопределенное поведение всегда мешает кормушке работать должным образом (или, скорее, позволяет ему «работать», как предполагалось).
- Если кормушка не работает, очень вероятно, что кошка умрет. Это не тот кот, который может постоять за себя, и я не смог попросить соседа посмотреть на него.
Я считаю, что кошка умирает с вероятностью 0,995 .