C
バックストーリー
妻は家族から猫を相続しました。†残念ながら、私は動物に対して非常にアレルギーがあります。猫はプライムをかなり過ぎており、それを手に入れる前から安楽死させられていたはずでしたが、感傷的な価値があるため、猫を駆除することはできませんでした。私は終了する計画孵化し、私のその苦しみを。
私たちは長期休暇に出かけていましたが、彼女は獣医のオフィスで猫に乗りたくありませんでした。彼女は病気にかかったり、虐待されたりすることを心配していました。家に置いておくことができるように、自動猫送り装置を作成しました。マイクロコントローラーのファームウェアはCで作成しました。含まれmain
ているファイルは以下のコードのように見えました。
しかし、私の妻もプログラマーであり、猫に対する私の気持ちを知っていたので、彼女はコードレビューを自宅に放置することに同意する前に主張しました。彼女には、次のようないくつかの懸念がありました。
main
標準に準拠した署名がない(ホストされた実装の場合)
main
値を返さない
tempTm
のmalloc
代わりに呼び出されたため、初期化されずに使用されますcalloc
- の戻り値は
malloc
キャストしないでください
- マイクロコントローラーの時間が不正確であるか、ロールオーバーする可能性があります(Y2KまたはUnix時間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の確率で死亡すると推定しています。