开发过程中,你是否会发出基础不牢,地动山摇的感慨,我相信,只要有经验的工程师,应该都有过。 曾经因为一个很基础的知识,差点毁了整个项目,这不是危言耸听。因为这个代码用于整个系统自检,一旦运行出错,整个系统就废了。 为了不让别人篡改代码,设计了多套机制,其中一个就是定时检查关键代码是否已执行,如果有一次没有执行,那么系统进入异常状态,这个功能类似窗口看门狗。uint16truncnt,runcntnext;voidfunction1(){dosomething;runcnt;自加,表示该函数已执行}intmain(){while(1){function1();if(runcnt!runcntnext1)判断两个变量是否匹配{doerrorsomething}runcntnext;这个位置也自加,表示这里已执行}} 类似流程如上,当时为了减少变量空间,将计数器设计成了uint16t类型,导致埋下了隐患。 这个流程乍一看没有问题,因为runcnt比runcntnext先加,那么runcntnext1应该等于runcnt,如果不相等,作错误处理。 甚至短时间内运行不会有任何问题,除非16位溢出 所以一个量产项目,任何一点改动,都可能需要长时间的稳定测试,只有这样才能确保系统稳定性,不能认为自己能力强,写的代码不用测试就直接合并了。 原先鱼鹰以为,这两个变量都是16位,那么1的结果应该也是16位,最后比较时,也是16位比较,这样即使最终16位自加溢出了,结果也会是正确的。if(runcnt!runcntnext1)判断两个变量是否匹配{doerrorsomething} 但你以为,终究是你以为。 实际上,因为你和1自加了,最终比较是按照32位进行比较,而runcnt受到变量位数限制,始终是16位的结果(但扩展成32位比较,即高16位全是0) 这样就会导致在溢出时,两者是不相等的。 比如上一次runcnt为0xFFFF时(受位数限制,最大只能是这个),runcntnext为0xFFFE,此次结果比较即使按32位比较,也是没有问题的,都是0xFFFF。 但下一次运行时,runcnt自加,溢出变成0,而runcntnext是0xFFFF,再和1相加,因为比较会使用32位比较,所以此时结果是0x10000,最终导致两者不相等(0!0x10000)。 那么为什么会导致上面的问题呢?这里涉及到两个C语言基础知识点,估计大家以前都了解过,但估计没有当回事。 1、常量默认为int型(但不一定是32bit,和内核和编译器有关,上面的1就是int型) 2、整型提升(详细可网上查找) 因为两边的结果类型不一致(1导致右边结果成了int类型),所以最终按int型处理。最终导致溢出时,结果判断失败。 我们可以通过汇编看出一些端倪: 我们可以看到r01之后,直接和r1比较,也就是说,结果可能超过0xFFFF,导致出错。 那么,怎么样才可以保证结果为16位呢? 我们可以这样处理:if((uint16t)runcnt!(uint16t)(runcntnext1))强制转化为16位比较{doerrorsomething} 我们可通过汇编发现,多了一条UXTH指令,用于把16位结果扩展成32位(从这里我们也可以得出结论,结果比较总是32bit比较)。 到此,分析结束,可以看到,为了解释这么一条简单的C语言语句,还是挺困难的事情。