씨
뒷이야기
아내는 가족으로부터 고양이를 물려 받았습니다. † 불행히도, 나는 동물에 매우 알레르기가 있습니다. 고양이는 그 전성기를 지났고 우리가 그것을 얻기 전에 안락사 되었음에도 불구하고 그녀는 감상적인 가치 때문에 고양이를 없애지 못했습니다. 나는 종료 할 계획 부화 내 그 고통을.
우리는 연장 휴가를 가고 있었지만 수의사의 사무실에서 고양이를 기르고 싶지 않았습니다. 그녀는 병이 위축되거나 학대받는 것에 대해 걱정했다. 집에두고 다닐 수 있도록 자동 고양이 피더를 만들었습니다. 마이크로 컨트롤러의 펌웨어를 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 확률로 죽을 것으로 추정한다 .