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