减法溢出翻转导致的死循环

下面的代码有没有问题?

#include <cstdio>

int main()
{
	unsigned int a = 10;
	int b = 10;
	/* loop 1 */
	while(a>0)
	{
		a -= 3;
		printf("a=%u\n", a);
	}
	/* loop 2 */
	while(b>0)
	{
		b -=3;
		printf("b=%d\n",b);
	}
	return 0;
}

循环1,假如a是某个数据的剩余长度,每次处理3个字节,处理完退出,好像没有什么问题,但是程序真的会如你所预料的那样运行吗?

当然不会,要不然你就不会看到这篇blog了。

程序输出a的值是:7,4,1,4294967294,4294967291,......

1-3溢出之后=4294967294,循环永远不会结束!

循环2,输出结果是:7,4,1,-2

为什么会出现这个问题?因为循环每次变化的大小是3,而unsigned int与循环结束条件之间的间隙只有1,如果循环每次变化的大小>1,循环结束条件将恒不成立。

如何避免:

1、使用加法代替减法,避免溢出翻转,类似for循环的i++;

2、使循环每次变化量<数据最小值与循环结束条件之间的间隙。比如loop 2的int最小值-2147483648与循环结束条件<=0之间的间隙是2147483648,远远大于循环步进3.

 

 

<think>好的,我现在要帮用户修复这段代码的QAC警告。首先,我需要仔细阅读用户提供的代码,理解它的功能,然后分析可能存在的QAC警告类型。用户给出的函数是Mw_GetAndEnd_MeasureTime,看起来是用来测量时间差的,可能涉及定时器或调度器的相关操作。 首先,我注意到代码中有几个静态变量:end_uc_unit、end_trj和end_trj1。这些变量被声明为static,可能在多次函数调用之间保持状态。但QAC可能会警告这些静态变量没有被初始化,或者可能存在并发访问的问题,特别是在多任务环境中。不过,这个函数可能是在单线程环境下使用,所以需要确认是否确实需要静态存储。如果这些变量只是临时存储每次调用的结果,那么声明为静态可能不合适,会导致数据持久化,进而引发问题。 接下来,代码中有条件编译的部分,使用了SCHEDULER_DEBUG_CODE宏。这部分代码可能只在调试时启用,但需要注意变量end_cnt和end_system_tick_cnt是否已经声明,否则在非调试编译时可能会报错。不过用户的问题是关于QAC警告,可能涉及未使用的变量或者宏定义相关的问题。 然后,函数内部调用了App_Scheduler_Get_TimerValue()两次,分别赋值给end_trj和end_trj1,之后比较这两个值。如果end_trj1小于end_trj,说明可能在两次调用之间发生了定时器的溢出,所以重新获取end_uc_unit的值。这部分逻辑可能用于处理定时器溢出的情况,但需要确认是否正确处理了所有可能的溢出情况。 在计算diff_units时,如果end_uc_unit大于等于start_uc_unit,直接相减;否则,将end_uc_unit加上0xFFFFFFFF再减去start_uc_unit。这里使用了uint64_t类型来避免溢出,但QAC可能对类型转换和显式转换有警告,比如强制转换是否正确,或者是否有更清晰的写法。 接下来,我需要考虑常见的QAC警告类型,比如: 1. 静态变量未初始化:静态变量在C语言中默认初始化为0,但显式初始化更安全,避免依赖编译器行为。 2. 变量未声明:比如end_cnt和end_system_tick_cnt是否在宏启用时已声明。 3. 类型转换问题:比如将uint64_t转换为uint32_t时可能的数据丢失。 4. 可能的整数溢出:在计算diff_units时,虽然使用了uint64_t,但转换回uint32_t是否安全? 5. 代码可读性问题:复杂的表达式可能需要拆分,或者添加注释。 6. 函数副作用:静态变量可能导致函数不可重入,如果在多线程或中断中使用会有问题。 7. 条件判断中的冗余代码:比如end_trj1的比较部分是否有必要,或者是否可以优化。 首先处理静态变量未初始化的问题。将static变量显式初始化为0u,比如: static uint32_t end_uc_unit = 0u; static uint32_t end_trj = 0u; static uint32_t end_trj1 = 0u; 这样避免QAC警告可能的“Static variable not explicitly initialized”。 然后,检查条件编译部分。如果SCHEDULER_DEBUG_CODE为1U,那么end_cnt和end_system_tick_cnt必须被声明,否则编译时会报错。需要确保这些变量在宏启用时有定义,或者在函数外部声明。如果用户没有提供这部分代码,可能需要添加条件编译的声明,或者确保在非调试情况下这些变量不会引发错误。不过这可能不是QAC警告的重点,但需要注意。 接下来,处理end_trj和end_trj1的比较部分。这里可能存在的警告是变量在赋值后未被使用,或者可能的竞争条件。比如,end_trj被赋值后,在if条件中比较end_trj1和end_trj,但end_trj1是在之后赋值的。这可能是因为担心在两次读取定时器之间发生溢出,所以需要重新获取end_uc_unit。这部分逻辑是否正确需要看定时器的实现,但代码本身可能没有问题,但QAC可能警告变量未使用,或者代码冗余。 例如,如果end_trj在赋值后,紧接着end_trj1被赋值,但之后end_trj只用于比较,之后不再使用。可能可以简化这部分代码,或者合并步骤。 然后,计算diff_units的部分,使用了强制转换到uint64_t,但最后返回的是uint32_t。QAC可能警告显式转换可能丢失数据,但在这个上下文中,由于计算的是时间差,且定时器的最大值是32位,所以结果应该在uint32_t范围内,所以转换是安全的。不过,可以添加注释说明这一点,或者使用显式的掩码操作,比如与0xFFFFFFFF进行按位与,来确保结果正确。 另外,代码中的表达式: diff_units = (uint32_t)( ((uint64_t)end_uc_unit + (uint64_t)0xffffffffu )- start_uc_unit ); 这里可能的问题在于,当end_uc_unit小于start_uc_unit时,实际上是计算(end_uc_unit - start_uc_unit)的补码,即相当于end_uc_unit + (0xFFFFFFFF - start_uc_unit + 1),也就是(end_uc_unit + (0xFFFFFFFF - start_uc_unit) +1),不过这可能更清晰的写法是(end_uc_unit - start_uc_unit)的模数运算,因为对于无符号数,减法会自动处理溢出。不过用户当前的写法是否正确? 例如,假设start_uc_unit是100,end_uc_unit是50(因为定时器溢出),那么正确的差值应该是50 + 0xFFFFFFFF -100 +1 = 0xFFFFFFFF -50 = 0xFFFFFFCE(十进制4294967246)。但用无符号数的减法,end_uc_unit - start_uc_unit在32位情况下会自动得到正确的结果,即50 -100 = 4294967196(因为无符号溢出)。因此,用户原来的代码可能可以简化为: diff_units = end_uc_unit - start_uc_unit; 因为对于无符号数,即使end_uc_unit < start_uc_unit,减法结果也会自动处理为模数运算,得到正确的时间差。这样就不需要复杂的条件判断和64位运算。这可能是一个优化点,同时也能消除QAC关于类型转换和复杂表达式的警告。 所以,原来的代码中的if-else分支可能没有必要,可以简化。这可能也是QAC警告的一个点,比如“Redundant code”或者“Overcomplicated expression”。 总结可能的修复步骤: 1. 显式初始化静态变量。 2. 简化时间差计算,直接使用无符号数的减法,去掉条件判断和64位运算。 3. 处理条件编译中的变量声明问题,确保宏启用时变量存在。 4. 检查end_trj和end_trj1的使用,是否有冗余或未使用的变量。 5. 添加注释提高代码可读性。 6. 确保所有类型转换安全,避免QAC警告。 现在,针对时间差计算的部分,如果改为直接使用减法,可以简化代码并消除相关警告。例如: uint32_t diff_units = end_uc_unit - start_uc_unit; 因为对于uint32_t类型,end_uc_unit - start_uc_unit会自动处理溢出,得到正确的时间差,不需要条件判断。这样不仅简化了代码,还避免了可能的QAC警告,比如“Useless type conversion”或者“Overcomplicated expression”。 然后,关于end_trj和end_trj1的比较部分,可能的问题在于,如果在两次调用App_Scheduler_Get_TimerValue()之间定时器发生了溢出,那么end_trj1可能会小于end_trj。此时,用户重新获取end_uc_unit的值,可能是为了确保在定时器溢出时获取最新的计数器值。这部分逻辑是否正确?假设App_Scheduler_Get_TimerValue()返回的是定时器的当前值,而SysTimer_GetCurrentCounter()是另一个定时器的值,或者同一定时器的不同部分。需要确认这里的逻辑是否正确,是否可能导致end_uc_unit在溢出时被正确更新。 例如,当end_trj1 < end_trj时,说明在两次获取定时器值之间,定时器可能发生了溢出(比如,定时器是递增的,达到最大值后归零)。此时,重新获取end_uc_unit(可能是另一个高精度定时器的值)以确保准确性。这部分逻辑可能需要保留,但可以优化代码结构,减少变量使用。 可能的QAC警告是end_trj被赋值后未被使用,除了在比较中。例如,end_trj被赋值为第一次App_Scheduler_Get_TimerValue(),然后end_trj1被赋值为第二次的值,之后比较两者,但之后end_trj不再使用。因此,可以合并代码,比如: end_trj = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); end_trj1 = App_Scheduler_Get_TimerValue(); 可以改为: do { end_trj = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); end_trj1 = App_Scheduler_Get_TimerValue(); } while (end_trj1 < end_trj); 但这样可能会引入循环,如果定时器一直在快速溢出,可能导致死循环。因此,用户原来的代码可能只是尝试在检测到溢出时重新获取end_uc_unit,但只执行一次。这可能不足以处理所有情况,但这是用户原有的逻辑。 不过,这样的代码可能导致QAC警告,比如变量end_trj被赋值后未被充分使用,或者代码冗余。可以考虑移除end_trj,直接比较end_trj1和之前的定时器值,但需要保持原有逻辑。 或者,可能可以简化为: end_uc_unit = SysTimer_GetCurrentCounter(); end_trj1 = App_Scheduler_Get_TimerValue(); if (end_trj1 < previous_trj_value) { end_uc_unit = SysTimer_GetCurrentCounter(); } 但这里可能需要更多的上下文。 总之,这部分代码可能引发QAC关于变量未使用或冗余赋值的警告。例如,end_trj被赋值后,仅在if条件中使用,之后不再使用。因此,可以尝试合并步骤,减少变量使用。比如,直接比较两次获取的定时器值: uint32_t trj_before = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); uint32_t trj_after = App_Scheduler_Get_TimerValue(); if (trj_after < trj_before) { end_uc_unit = SysTimer_GetCurrentCounter(); } 这样,变量名更清晰,且避免了使用end_trj和end_trj1,减少变量数量,可能消除QAC关于变量未使用的警告。 接下来,处理静态变量的问题。如果这些变量确实需要在多次调用间保持状态,那么可能需要保留static,但需要考虑线程安全。如果函数被多个线程调用或在中断中使用,静态变量会导致竞争条件。但用户的问题可能不涉及多线程,所以显式初始化即可。 综合以上分析,修复步骤可能包括: 1. 显式初始化静态变量。 2. 简化时间差计算,使用无符号减法。 3. 优化定时器读取逻辑,减少变量使用。 4. 处理条件编译部分的变量声明。 5. 添加必要的注释。 现在,将这些步骤转化为代码修改: 原代码: static uint32_t end_uc_unit = 0u; static uint32_t end_trj = 0u; static uint32_t end_trj1 = 0u; 修改为显式初始化: static uint32_t end_uc_unit = 0u; static uint32_t end_trj = 0u; static uint32_t end_trj1 = 0u; (已经初始化,可能没有问题) 时间差计算部分: 原代码: if(end_uc_unit >= start_uc_unit) { diff_units = end_uc_unit - start_uc_unit; } else { diff_units = (uint32_t)( ((uint64_t)end_uc_unit + (uint64_t)0xffffffffu )- start_uc_unit ); } 可以简化为: diff_units = end_uc_unit - start_uc_unit; 因为对于无符号数,减法会自动处理溢出,得到正确的时间差。因此,整个if-else结构可以删除,直接赋值。 这将消除QAC关于复杂类型转换和条件冗余的警告。 关于定时器读取部分: 原代码: end_trj = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); end_trj1 = App_Scheduler_Get_TimerValue(); if(end_trj1 < end_trj) { end_uc_unit = SysTimer_GetCurrentCounter(); } 可以优化为: uint32_t trj_before, trj_after; do { trj_before = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); trj_after = App_Scheduler_Get_TimerValue(); } while (trj_after < trj_before); 这样可以确保在定时器没有溢出的情况下获取一致的trj_before和trj_after,避免在读取过程中定时器溢出导致的时间差错误。但使用循环可能会增加执行时间,特别是在高频定时器下。不过,这可能更可靠,而原代码只检查一次,可能不够。不过,用户原有逻辑可能只是尝试一次重新读取,因此可能需要保持原逻辑,但可以更改变量名以减少冗余。 或者,将end_trj和end_trj1改为局部变量,而非静态,因为它们在每次调用中都是临时的: uint32_t trj_before = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); uint32_t trj_after = App_Scheduler_Get_TimerValue(); if (trj_after < trj_before) { end_uc_unit = SysTimer_GetCurrentCounter(); } 这样可以消除静态变量end_trj和end_trj1,从而避免QAC关于静态变量可能导致的并发问题或持久化问题。因为原来的静态变量可能没有必要,因为每次调用函数时,这些值都是新的,不需要保持状态。所以,将end_trj和end_trj1改为局部变量,可以解决静态变量相关的QAC警告。 这样修改后,静态变量只剩下end_uc_unit,但原代码中end_uc_unit是static,可能用于保存最后一次的值,但函数名为GetAndEnd_MeasureTime,可能意味着每次调用都会更新结束时间,所以end_uc_unit作为静态变量可能保存了上一次调用的值,这可能导致错误。例如,如果函数被多次调用,静态变量会保留之前的值,导致计算的时间差不正确。 哦,这里发现一个严重问题:start_uc_unit可能是在另一个函数中保存的开始时间,而end_uc_unit是当前结束时间。如果end_uc_unit是静态变量,那么每次调用Mw_GetAndEnd_MeasureTime都会更新它,但start_uc_unit可能是在Mw_Start_MeasureTime中保存的静态变量?需要确认start_uc_unit的来源。如果start_uc_unit是全局变量或静态变量,而Mw_GetAndEnd_MeasureTime函数用来计算当前时间与start_uc_unit的差,那么end_uc_unit作为静态变量可能没有必要,应该每次获取当前值。 例如,正确的逻辑应该是: start_uc_unit在开始测量时记录,然后在结束时获取当前的end_uc_unit,计算差值。因此,end_uc_unit不应是静态变量,而应该是每次函数调用时的当前值。将其声明为静态会导致每次调用函数时,end_uc_unit保留上一次的值,这显然错误,会导致计算的时间差不正确。 因此,原代码中的end_uc_unit、end_trj、end_trj1被错误地声明为静态变量,这会导致函数不可重入,且在多次调用时返回错误的结果。这是严重的逻辑错误,也会导致QAC警告,比如“Persistent static variable may lead to unpredictable behavior”或类似的。 因此,必须将这些静态变量改为局部变量。例如: uint32_t Mw_GetAndEnd_MeasureTime(void) { uint32_t end_uc_unit; uint32_t trj_before, trj_after; #if (SCHEDULER_DEBUG_CODE == 1U) end_cnt++; end_system_tick_cnt = system_tick_cnt; #endif trj_before = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); trj_after = App_Scheduler_Get_TimerValue(); if (trj_after < trj_before) { end_uc_unit = SysTimer_GetCurrentCounter(); } uint32_t diff_units = end_uc_unit - start_uc_unit; return diff_units; } 这样修改后,去除了不必要的静态变量,解决了静态变量导致的QAC警告,同时修复了函数逻辑错误。 总结问题: 1. 静态变量错误使用,导致函数不可重入且结果错误。 2. 时间差计算过于复杂,可以简化为无符号减法。 3. 可能的变量未初始化(但原代码中静态变量默认初始化为0)。 4. 条件编译部分的变量声明问题。 5. 冗余的变量和代码结构。 修复步骤: 1. 将静态变量改为局部变量,因为每次调用函数都应获取当前时间。 2. 简化时间差计算,使用无符号减法。 3. 确保条件编译中的变量已声明。 4. 优化定时器读取逻辑,使用局部变量避免静态存储。 5. 添加注释说明关键步骤。 修改后的代码可能如下: uint32_t Mw_GetAndEnd_MeasureTime(void) { uint32_t end_uc_unit; uint32_t trj_before, trj_after; #if (SCHEDULER_DEBUG_CODE == 1U) /* 调试代码:增加计数并记录系统节拍 */ static uint32_t end_cnt = 0u; // 如果end_cnt未声明,需在此处或外部声明 extern uint32_t end_system_tick_cnt; // 假设在外部定义 end_cnt++; end_system_tick_cnt = system_tick_cnt; // 假设system_tick_cnt已定义 #endif /* 获取定时器值,处理可能的溢出 */ trj_before = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); trj_after = App_Scheduler_Get_TimerValue(); /* 如果发生溢出,重新获取当前计数器值 */ if (trj_after < trj_before) { end_uc_unit = SysTimer_GetCurrentCounter(); } /* 计算时间差(无符号减法自动处理溢出) */ uint32_t diff_units = end_uc_unit - start_uc_unit; return diff_units; } 这样修改后: - 静态变量被移除,解决了不可重入问题。 - 时间差计算简化,消除QAC警告。 - 添加了注释,提高可读性。 - 处理了条件编译中的变量声明问题(假设end_cnt和end_system_tick_cnt在适当的位置声明)。 需要注意的是,start_uc_unit应该是另一个函数(如Mw_Start_MeasureTime)中记录的静态变量或全局变量,否则函数无法正确计算时间差。因此,需要确保start_uc_unit的存储方式正确,但这可能超出当前函数的修改范围。 最后,检查所有可能的QAC警告是否已解决: - 静态变量移除,解决相关警告。 - 简化计算,消除类型转换和复杂表达式警告。 - 显式声明和初始化变量,避免未初始化警告。 - 代码结构更清晰,减少冗余。 因此,最终的修复代码应该类似上述修改后的版本,同时确保所有使用的变量正确声明,尤其是在调试宏启用时。</think>以下是针对该代码的QAC警告修复方案及代码优化建议,主要解决静态变量滥用、时间差计算优化和代码可读性问题: ```c uint32_t Mw_GetAndEnd_MeasureTime(void) { uint32_t end_uc_unit; uint32_t trj_before, trj_after; #if (SCHEDULER_DEBUG_CODE == 1U) /* 调试代码:增加计数并记录系统节拍 */ static uint32_t end_cnt = 0u; // 需在文件作用域声明 extern uint32_t end_system_tick_cnt;// 需在外部定义 end_cnt++; end_system_tick_cnt = system_tick_cnt; // 假设system_tick_cnt已定义 #endif /* 获取定时器值序列(处理定时器翻转)*/ trj_before = App_Scheduler_Get_TimerValue(); end_uc_unit = SysTimer_GetCurrentCounter(); trj_after = App_Scheduler_Get_TimerValue(); /* 检测定时器翻转并重新采样 */ if (trj_after < trj_before) { end_uc_unit = SysTimer_GetCurrentCounter(); } /* 无符号减法自动处理计数器翻转 */ return end_uc_unit - start_uc_unit; } ``` 主要修改点说明: 1. **静态变量问题** - 原代码问题:错误使用`static`存储类 - `end_uc_unit`/`end_trj`/`end_trj1`改为局部变量 - 避免函数不可重入问题(多次调用相互影响) - 解决QAC警告:`[MISRA-C:2012 Rule 8.10]`(函数不可重入风险) 2. **时间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值