С
Попередня історія
Моя дружина успадкувала кота від родини. † На жаль, я дуже алергічний до тварин. Кішка вже минула свій розквіт і мала бути евтаназованою ще до того, як ми її отримали, але вона не могла змусити себе позбутися її через свою сентиментальну цінність. Я дозрів план , щоб закінчити мій його страждання.
Ми їхали у розширену відпустку, але вона не хотіла сідати з котом у кабінет ветеринара. Вона була стурбована тим, що вона захворіє на хворобу чи жорстоко поводиться з нею. Я створив автоматичну годівницю для котів, щоб ми могли залишити її вдома. Я написав мікропрограмне забезпечення мікроконтролера в 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 .