C代码中忽略模数操作数

时间:2013-09-05 18:32:36

标签: c integer-arithmetic operands

我有以下代码:

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 个答案:

答案 0 :(得分:3)

在执行任何算术之前,

short数据类型将转换为int。由于您的模数为16,因此它是int而不是unsigned

如果您不能使用short,请仅使用unsigned类型进行mod操作,16U

答案 1 :(得分:2)

首先,对于任何计算,短值都会​​提升为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。

答案 2 :(得分: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处理器有更深的管道和缓存未命中需要担心)。但是,最好让代码工作(并且不会轻易破解)而不是无效的代码。

答案 3 :(得分:0)

我有两条建议。对于这个问题,任何一个都可以工作。两次幂除数的最快值是:

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

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

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

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

我正在回答一个问题,这个问题的答案对于提问者来说可能已经足够好了,但答案一般都不好。转换为无符号值的建议适用于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的更长解决方案将运行得更快。答案顶部的解决方案将按固定金额处理所有模块减法的情况。