C语言打印16进制出现0xffffff现象的有关问题剖析

C语言打印16进制出现0xffffff现象的问题剖析!

  今天在博问里面看到一个朋友的问题,大致是在网络程序中,打印出来的16进制数,莫名的出现ffffff。例如,某个byte真是值为0xc9,打印出来确是0xffffffc9。原博问连接如下:http://q.cnblogs.com/q/71073/

  其实类似的问题不是只在网络程序中才会出现的,看示例代码:

  1 #include <stdio.h>
  2 int main()
  3 {
  4     char c = 0xc9;
  5     printf("A:c = %2x\n",(unsigned char)c);
  6     printf("B:c = %2x\n",c & 0xff);
  7     printf("C:c = %2x\n",c);
  8     return 0;
  9 }

  程序输出如下:

A:c = c9
B:c = c9
C:c = ffffffc9

  可以看到:

  把c转换成unsigned char打印是正确的。视作情况A。

  把c与 0xff做&操作后打印正确。视作情况B。

  对c不做任何处理,则问题复现了,打印出ffffffc9。视作情况C。

  情况A B是我百度来的一些解决C现象的方法。那么我们现在来逐一分析解释ABC三种情况。

  

  首先我们必须知道,printf()函数的%x(X)输出的是Int型别的16进制格式。所以char型别的c变量会被转换成Int型别。

  其次,我们的知道计算机是用补码表示数据的。关于原码,反码,补码的知识请自行充电。

情况C:

   c的补码:11001001(0xc9)。

   c的反码:11001000(0xc9)。

   c的原码:10110111(0xc9)。 

   因为char型别是带符号的,所以最高位的1这里视为负号。

   把c转换成Int型别   char  -----> Int

   Int_c的原码:10000000 00000000 00000000 00110111(把c原码的最高位1  提到最高位。其余高位补0)。

   Int_c的反码:11111111 11111111 11111111 11001000

   Int_c的补码:11111111 11111111 11111111 11001001(0xffffffc9)。

   所以打印出来看似诡异的值其实是合情合理的。如何避免?看AB情况。

情况B:

  我们在情况C的基础上将c与0xff做&操作。

  Int_c的补码:11111111 11111111 11111111 11001001(0xffffffc9)。

         &

         00000000 00000000 00000000 11111111

  最终结果为: 00000000 00000000 00000000 11001001(0xc9)。

情况A:

  我觉得情况A的处理方式才是最正规的处理办法,但是据说linux内核使用(&0xff)。

   c的补码:11001001(0xc9)。

   c的反码:11001001(0xc9)。

   c的原码:11001001(0xc9)。 

   这里强制转换c为unsigned char型别。因此最高位的1不是正负号

   把c转换成Int型别   char  -----> Int

   Int_c的原码:00000000 00000000 00000000 11001001(把c原码的最高位1  提到最高位。其余高位补0)。

   Int_c的反码:00000000 00000000 00000000 11001001

   Int_c的补码:00000000 00000000 00000000 11001001(0xc9)。

   因此打印正常。

  以上分析,如有不正确的地方请各位指正。

 

6楼女孩不哭
c的定义都错了,此处明显该unsigned...,只有极少数的编译器默认char为unsigned。
Re: 不爱洗脸
@女孩不哭,不知道你说什么
5楼CamelOnTheWay
评论比原文精彩。
Re: 不爱洗脸
@CamelOnTheWay,不要净瞎说实话
4楼garbageMan
情况B:,这部分没什么问题,,不管char 的行为与signed char一致还是与unsigned char一致,,都会得到那个结果
3楼Grid Science
应该使用 amp; 0xff 的方法。,,先讲一点原理。,第一,根据你的结果,你所使用的编译器默认 char 是有符号的。关于 char 默认有没有符号这个没有规定,所以最好的方法是写出完全的定义:,unsigned char c = 0xc9;,或者,signed char c = 0xc9;,觉得麻烦可以用 typedef。,第二,%x 格式指示符认为对应的参数类型是 int(在32位条件下,4字节;64位条件下,8字节)。在进行打印的时候,会进行自动转换 signed char -gt; int。,为什么这里不指出是 signed int 还是 unsigned int,因为这里无所谓。接下来做的是直接“看”内存,符号位没有了特殊性。所以最后输出的是转换后的单元中(很可能被编译放到了寄存器中)的原始内容。,,然后看两种得到了预期输出的方法。,第一种转换为 unsigned char,在往 int 转换的过程中其符号位为0所以恒为正数。但是我觉得这种方法不好。为什么呢?,1、它的含义不明显,因为后文中都没有用到其 unsigned 的意义,写在这里如果不写上“为了得到 c 类型为 char 时的正确输出”这样的注释的话,一般看不出这个强制转换是干什么的。,2、这个方法只对扩充转换有用。如果是这样的代码:,int c = 0xccccccc9; printf(quot;%8xquot;, c);,或者是这样的代码:,int c = 0xccccccc9; printf(quot;%8xquot;, (unsigned int)c);,二者输出是相同的(见上面的原理解释)。所以当基类型相同、只是有符号无符号的差别时,这个转换是完全不需要的。,如果是收缩的转换,如 int -gt; char,则高位会直接被截断,然后再次扩展。,以下代码会输出“ffffffc9”:,int c = 0xccccccc9; printf(quot;%2xquot;, (char)c); // 同时“%2x”失效,退化成“%x”,第二种方法则反映了 printf 的实质。直接进行位操作,告诉CRT我只要低8位,其他全部为零,自然会得到正确的结果。而且这也不会引起人的误解,在对于任意的整数类型通用。
Re: 不爱洗脸
@Grid Science,说的很严谨,受教!
2楼garbageMan
情况A:,你的分析不对。,,本质上,printf(quot;A:c = %2xquot;,(unsigned char)c);,等价于,printf(quot;A:c = %2xquot;,(int)(unsigned char)(int)c);
Re: 不爱洗脸
@garbageMan,这个没看懂啊。
1楼garbageMan
情况C:,引用因为char型别是带符号的,所以最高位的1这里视为负号。,这个说法是不对的。,在有的实现中,char 的行为与signed char一致,有的实现中char 的行为与unsigned char一致。,char和int在这一点上是不一致的。,printf(quot;C:c = %2xquot;,c);,等同于,printf(quot;C:c = %2xquot;,(int)c);,这一点你说得对,但不一定一定输出ffffffc9,对于某些编译器来说,输出c9也没什么问题。
Re: 不爱洗脸
@garbageMan,以前还真不知道这个。char和int标准里确实不一样。