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。