1

我有以下代码:

unsigned short wrLine;
unsigned short prev = ((wrLine - 1) % 16);
wrLine = (wrLine + 1) % 16;

这会生成以下反汇编:

unsigned short prev = ((wrLine - 1) % LINES_IN_FIFO);

0041456A   movw      r3, #25282           
0041456E   movt      r3, #8192            
00414572   ldrh      r3, [r3]             
00414574   uxth      r3, r3        
00414576   add.w     r2, r3, #4294967295        
0041457A   mov.w     r3, #15              
0041457E   movt      r3, #32768           
00414582   ands      r3, r2        
00414584   cmp       r3, #0        
00414586   bge       #10           
00414588   add.w     r3, r3, #4294967295        
0041458C   orn       r3, r3, #15          
00414590   add.w     r3, r3, #1           
00414594   strh      r3, [r7, #4]   

wrLine = (wrLine + 1) % LINES_IN_FIFO;

0041463E   movw      r3, #25282           
00414642   movt      r3, #8192            
00414646   ldrh      r3, [r3]             
00414648   uxth      r3, r3        
0041464A   add.w     r2, r3, #1           
0041464E   mov.w     r3, #15              
00414652   movt      r3, #32768           
00414656   ands      r3, r2        
00414658   cmp       r3, #0        
0041465A   bge       #10           
0041465C   add.w     r3, r3, #4294967295        
00414660   orn       r3, r3, #15          
00414664   add.w     r3, r3, #1           
00414668   uxth      r2, r3        
0041466A   movw      r3, #25282           
0041466E   movt      r3, #8192  

有趣的是,如果 wrLine 为零,则 prev 最终将等于 0xFFFF,而当 wrLine 为 15 时,它将最终等于 0x0000。知道为什么只有其中一个有效吗?

谢谢,德文

4

4 回答 4

3

shortint在执行任何算术运算之前将数据类型转换为。由于您的模数是16,因此它是 aint而不是unsigned

如果你不能使用,请不要使用short,并且只对类型进行 mod 操作unsigned,here 16U

于 2013-09-05T18:39:03.427 回答
2

首先,对于任何计算,short 值都被提升为 int。编译器转换%16为等效的按位运算&15,因为它在目标 CPU 上可能更有效。实际上它被转化为& 0x8000000F: 这保持了符号位。结果与零比较:如果大于0,则直接使用结果的下半部分;否则,所有高位都用 0xF 的按位 OR-NOT 设置。这使得结果的符号正确。

如果wrLine为 0,则减去 1 将值提升为有符号int类型,计算结果为 -1。以上计算-1 % 16 = -1。当 -1 存储在无符号短整数中时,结果为 0xFFFF。

如果wrLine是 15,加 1 计算 16,并且 16 % 16 = 0。

于 2013-09-05T18:40:10.903 回答
0

试试这个改变

unsigned short wrLine;
unsigned short prev = ((unsigned short)(wrLine - 1) % 16);
wrLine = (wrLine + 1) % 16;

或者更好(因为它适用于 FIFO_SIZE 的所有值)。

int wrLine;
int prev = wrLine-1; if (prev<0) prev = FIFO_SIZE-1;
wrLine++; if (wrLine >= FIFO_SIZE) wrLine = 0;

%很昂贵,因为它涉及分裂。 &更快,但它仅适用于 2 的幂。

根据您的架构,条件分支可能有成本,也可能没有成本(8051 微控制器的分支成本并不高,而 x86/x64 处理器需要担心更深的管道和缓存未命中)。但是,拥有可以工作(并且不容易破坏)的代码而不是不工作的代码总是更好。

于 2013-09-05T18:43:03.593 回答
0

我有两个建议。对于这个问题,任何一个都可以。二次除数的最快速度是:

wrLine = (wrLine - 1)&(16-1); /* (wrLine + 1) mod 16 */

尽管有 JG 的评论,这 (a) 始终有效,并且 (b)在 wrLine 签名时永远不会由编译器生成。另一种方法是从不减去。而是使用:

wrLine = (wrLine + 16 - 1)%16;

只要 wrLine 最初不是负数,就不会产生负数结果。这是除数不是2 的幂时使用的模式。

我正在回答一个问题,该问题的答案可能对提问者来说已经足够好,但总的来说这个答案并不好。转换为无符号值的建议适用于二次除数的幂。从有符号到无符号的转换(在数值上)相当于加上 2 的大幂。这相当于仅当除数等于或小于 2 的幂时才加 0。

例如:

printf("-1 % 23U = %d", -1 % 16U);

产生:

-1 % 23U = 15

...这显然不是 22。我所知道的 (a mod b) 最简洁的表达是:

(a%b + b)%b /* when b>0 */
(a%b - b)%b /* when b<0 */

这些涉及两个部门,因此使用 if 或 if-else 的更长解决方案将运行得更快。答案顶部的解决方案将处理固定数量的模减法的所有情况。

于 2013-09-05T20:08:22.733 回答