映月读书网 > C语言解惑 > 17.1 位运算典型错误 >

17.1 位运算典型错误

【例17.1】程序员想编写如下程序实现快速运算2*2+1=5,能实现吗?


#include <stdio.h>
int main
()
{
      int x=1
,y
;
      y=x<<2+1
;
      printf
(\"y=%dn\"
, y
);
      return 0
;
}
  

【解答】不能。“+”运算符的优先级高于“<<”。“y=x<<2+1;”与语句


y=x<<
(2+1
);
  

等效,结果为8。要想使结果为5,应保证执行顺序,即修改为


y=
(x<<2
)+1
;
  

【例17.2】下面程序使用一个变量保存8个权限标识。原意是要为指定的用户设置“P_ADMIN”和备份“P_BACKUP”两个特权,然后验证是否正确设置数据,但却没有事先预定设想,为什么会发生这种情况?


#include <stdio.h>
#define CI const int
CI P_USER=
(1<<1
);
CI P_REBOOT=
(1<<2
);
CI P_KILL=
(1<<3
);
CI P_TAPE=
(1<<4
);
CI P_RAW=
(1<<5
);
CI P_DRIVER=
(1<<6
);
CI P_ADMIN=
(1<<7
);
CI P_BACKUP=
(1<<8
);
int main
()
{
    unsigned char privs = 0
;
    privs |= P_ADMIN
;
    privs |= P_BACKUP
;
    printf
(\"Privileges
: \"
);
    if 
(( privs & P_ADMIN
) 
!=0 
)
             printf
(\"Administration \"
);
    if 
(( privs & P_BACKUP
) 
!=0 
)
             printf
(\"Backup \"
);
    printf
(\"n\"
);
    return 0
;
}
  

【解答】一个字符有0到7位(共有8位),用常量(1<<0)到(1<<7)来表示8位,在使用1<<8则超出一个字符位的范围。所以语句


CI P_BACKUP=
(1<<8
);
  

的定义无效,表达式


privs |= P_BACKUP
;
  

是无效的,结果只是设置了系统管理员权限,运行结果为:


Privileges
: Administration
  

【例17.3】找出下面程序中的错误。


#include <stdio.h>
struct status {
    int on_line
:1
;
    int ready
:1
;
    int paper_out
:1
;
    int manual_feed
:1
;
}
;
int main
()
{
    struct status printer_status
;
    printer_status.on_line = 1
;
    if
( printer_status.on_line == 1
)
           printf
(\"Printer is on_line.n \"
);
    else
           printf
(\"Printer down.n \"
);
    return 0
;
}
  

【解答】带符号位的1位数字可能是0,也可能是-1。由于语句


printer_status.on_line = 1
;
  

的1位长的字段不可能保存值1而失败(因它的值出现溢出并将变量赋值为-1),结果就导致判断语句“if(printer_status.on_line==1)”的失败,输出“Printer down.”。

单字节字段应该是无符号型的,将它们使用unsigned定义。


//
修改的程序
#include <stdio.h>
struct status {
       unsigned int on_line
:1
;
       unsigned int ready
:1
;
       unsigned int paper_out
:1
;
       unsigned int manual_feed
:1
;
}
;
int main
()
{
     struct status printer_status
;
     printer_status.on_line = 1
;
     if
( printer_status.on_line == 1
)
             printf
(\"Printer is on_line.n \"
);
     else
             printf
(\"Printer down.n \"
);
     return 0
;
}
  

【例17.4】分析下面程序的输出结果。


#include <stdio.h>
int main
()
{
      char ch = \'A\'
;
      printf
(\"%c \"
, 
(ch | 0x0
));
      printf
(\"%c \"
, 
(ch | 0x4
));
      printf
(\"%c \"
, ch+2
);
      printf
(\"%cn\"
, ch+4
);
      return 0
;
}
  

【分析】第1条打印语句输出字符A,第2条等效于第4条,都是输出字符E,第3条输出字符C。输出结果为“A E C E”。

【例17.5】分析下面程序的输出结果。


#include <stdio.h>
int main
()
{
     int flags = 0x5
;
     printf
(\"-parityn\" + 
(( flags & 0x1 
) 
!= 0 
));
     printf
(\"-breakn\"  + 
(( flags & 0x2 
) 
!= 0 
));
     printf
(\"-xonn\"    + 
(( flags & 0x4 
) 
!= 0 
));
     printf
(\"-rtsn\"    + 
(( flags & 0x8 
) 
!= 0 
));
     return 0
;
}
  

【解答】printf函数调用任何字符串时,如果给一个字符串加1,打印字符串就会丢失原字符串的第1个字符。语句


printf
(\"a stringn\"
);
printf
(\"a stringn\" + 1
);
  

的输出结果为:


a string
  string
  

表达式((flags&0x4)!=0)的返回值根据该位是否被设置而定,可能为0或1。如果该位已经设置(“-xonn”+0),该程序就打印-xon;如果已清除(“-xonn”+1),则打印xon。

这里的输出结果如下:


parity
-break
xon
-rts
  

由此可见,为了增加程序的易读性,应添加必要的注释。

【例17.6】请分析下面程序输出的不是0xc,而是0x3c的原因。


#include <stdio.h>
int main
()
{
    int x=0xf
;
    x=x<<2
;
    printf
(\"%#xn\"
, x
);
    return 0
;
}
  

【解答】将一个数的每个位全部左移若干位,如x<<2表示将x中各二进位左移2位,如果x值为16进制数0xf,左移2位后,右边空出来的位补0。在这4位中,确实是c。但寄存器是32位,左移将11移到上一位,打印出来就是0x3c。如果使用x=0xffffffff,左移2位就是0xfffffffc。对0xf而言,要想得到只有4位的值,可以使用“&”运算符,即使用


x=
( x & 0x0f
);
  

语句将11置为00,打印的就是0xc。

【例17.7】请分析下面程序的输出结果。


#include <stdio.h>
int main
()
{
     int x=0xffffffff
, y=0xf
;
     int a=0225
, b=-60
;
     x=x>>3
;     y=y>>3
;
     printf
(\"x=%#x y=%#xn\"
, x
, y
);
     a=a>>2
;     b=b>>2
;
     printf
(\"a=%#o b=%#dn\"
, a
, b
);
     return 0
;
}
  

【解答】向右移出去的位将丢失,但问题是左边空出的位如何填充。也就是向右移位时,空出的位是用0填充,还是用1填充。其实,这个问题有时与具体的C语言的实现有关。

如果被移的变量是无符号数,空出的位用0填充,这种补0的方式称为“逻辑右移”。如果被移的变量是有符号数,那么C语言实现既可用0填充,也可用1(符号位的副本)填充(这种补1以保持负号的方法称“算术右移”。如果将变量声明为无符号类型,则采取“逻辑右移”方式。

这里使用的系统对负数采取“算术右移”。由此可知x采取补1,移位后不变的方式。y移走3位,左边补0,为0x1。a是8进制正数,0225代码为10010101。右移得00100101,即045。b是10进制负数,负数以补码形式表示,-60的补码为11000100,右移2位为11110001,即10进制-15。由此可得出程序的输出结果为:


x=0xffffffff y=0x1
a=045 b=-15
  

【例17.8】移位计数(即移位操作的位数)允许的取值范围是什么?

【解答】这要看整数的位数。举例来说,如果一个int型整数是32位,n是一个int型整数,那么n<<31和n<<0的写法都是合法的,而n<<32和n<<-1的写法则是非法的。

需要注意的是,即使C语言实现将符号位复制到空出的位中,有符号整数的向右移位运算也并不等同于除以2的某次幂。要证明这一点,可以考虑(-1)>>1,这个操作的结果一般不可能为0,但是(-1)/2在大多数C实现上求值结果都是0。这意味着以除法运算来代替移位运算,将可能导致程序运行大大减慢。假设a+b>0,则


c = 
( a + b 
)>>1
;
  

与下式


c = 
( a + b 
) / 2
;
  

完全等效,而且前者的执行速度也要快得多。