Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8101336
  • 博文数量: 159
  • 博客积分: 10424
  • 博客等级: 少将
  • 技术积分: 14615
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-14 12:45
个人简介

啦啦啦~~~

文章分类
文章存档

2015年(5)

2014年(1)

2013年(5)

2012年(10)

2011年(116)

2010年(22)

分类: C/C++

2011-10-31 22:41:22

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net       linuxfocus.blog.chinaunix.net

今天再次遇到一个C语言的细节问题,并且发现自己以前的理解不正确,然后总结了一下,写出本文。请看下面的代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. int main()
  4. {
  5.     unsigned int a = 1;
  6.     unsigned int b = -1;

  7.     printf("a is 0x%X\n", a);
  8.     printf("b is 0x%X\n", b);

  9.     printf("a-b is 0x%X\n", a-b);

  10.     return 0;
  11. }
它的结果为:
  1. a is 0x1
  2. b is 0xFFFFFFFF
  3. a-b is 0x2
其中最后的a-b的结果为0x2,不是我之前期望的。因为a为1,而b为无符号数中的最大数,它的结果怎么会是2呢?

如果有朋友认为结果是2的原因,是因为1-(-1)=1+1=2。那么我只能说恭喜你,虽然你的推导与结果相同,但是想法是错误的。因为这里是无符号数,不是有符号数,所以这里没有-1。

经过和一些朋友的讨论,我又写了一些测试代码:
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. int main()
  4. {
  5.     unsigned short a = 1;
  6.     unsigned short b = -1;

  7.     printf("a is 0x%X\n", a);
  8.     printf("b is 0x%X\n", b);

  9.     printf("a-b is 0x%X\n", a-b);

  10.     return 0;
  11. }
这里只是将int改为short型,结果则是:
  1. a is 0x1
  2. b is 0xFFFF
  3. a-b is 0xFFFF0002
先分析一下这个结果,看看是否对于之前的问题有所帮助。a-b的时候,按照c标准会将其变为int型,即(int)a-(int)b=1-65535=-65534=0xFFFF0002。而%x打印的是无符号整数,结果自然是0xFFFF0002。

第二份代码与第一份代码,不同之处,只是a和b的类型不同。在第一份代码中,a-b的时候,因为其类型为无符号整数,所以这里不会发生整数提升,仍然是无符号数的减法。而1-0xFFFFFFFF应该等于-0xFFFFFFFE。而这个数值实际上可以理解为超出了无符号数unsigned int的表示范围——注意,这里表达的并不准确,因为unsigned int的范围是0~0xFFFFFFFF。也就是说unsigned int是无法表示-0xFFFFFFFE的数值的。

为了证明这一点,我们可以扩大一下a和b的类型:
  1. #include <stdlib.h>
  2. #include <stdio.h>


  3. int main()
  4. {
  5.     unsigned int a = 1;
  6.     unsigned int b = -1;

  7.     printf("a is 0x%X\n", a);
  8.     printf("b is 0x%X\n", b);

  9.     printf("a-b is 0x%llX\n", (unsigned long long)a-(unsigned long long)b);

  10.     return 0;
  11. }
其结果为:
  1. a is 0x1
  2. b is 0xFFFFFFFF
  3. a-b is 0xFFFFFFFF00000002
这与我们前面的分析吻合。

从上面的分析看出,这个简单的无符号整数的减法操作,引出了这么多的东西。如果说前面的分析或者推导有些难以理解的话,还有一种理解方式,但是我不知道正确与否,合适与否。这个a-b的值一定要满足a-(a-b)=b。当a=1,而b等于0xFFFFFFFF时,所以这个(a-b)一定为2。——你不要觉得这样的等式一定成立,在计算机的编程中,这种算术运算的反运算,是有可能不成立的。

今天有看了看二进制补码以及减法的实现——依然是通过加法实现,感觉对这个问题似乎可以理解的更深。但是目前还是有一层窗户纸,没有捅破,就差一点了。


阅读(36095) | 评论(9) | 转发(5) |
给主人留下些什么吧!~~

panqihe2014-06-12 10:58:51

2、理解unsigned short是2字节大小的数据类型
   unsigned short a = 1;  // 1
   unsigned short b = -1; // 65535
   printf("a-b is 0x%X\n", a-b); 
   打印语句这里的a-b。在运算之前,a 和 b都强制转为unsigned int类型,所以b就是65535,不再是-1了。
   所以,结果就是1 - 65535 = -65534,表示为unsigned int类型就是0xFFFF0002。
   后面unsigned long long可以一样分析。

panqihe2014-06-12 10:47:20

楼主把人带沟里去了。
1、理解unsigned int 是4字节大小的数据类型
   unsigned int a = 1;    // a = 1;
   unsigned int b = -1;   // a = 4294967295;
   既然是无符号数,就得按无符号数理解,实际就是无符号减溢出问题。
   a - b = 1 - 4294967295= -4294967295。内存存储-4294967295,就是无符号2。

2、理解unsigned short是2字节大小的数据类型
   unsigned short a = 1;    // a&nb

gfree_wind2013-09-29 12:47:00

yjcpu:楼主的分析有些问题:
1 { 如果有朋友认为结果是2的原因,是因为1-(-1)=1+1=2。那么我只能说恭喜你,虽然你的推导与结果相同,但是想法是错误的。因为这里是无符号数,不是有符号数,所以这里没有-1。 }
关于这部分,这个分析并没有错,按照补码的计算,-(-1)相当于对1取了两次补码,所以结果还是1,最终结果是1+1=2没错

2 关于后两个实验的结果的不同,我只看到你的printf使用的格式化参数不同而已,跟计算的数据类型根本没有关系

使用格式参数是为了显示出计算机真正存储的数值,你要愿意的话,把其换成%d也可以。
int main()
{
    unsigned short a = 1;
    unsigned short b = -1;

    printf("a is 0x%X\n", a);
    printf("b is 0x%X\n", b);

    printf("a-b is %d\n", a-b);

    return 0;
}

结果是
[fgao@fgao test]#./a.out
a is 0x1
回复 | 举报

yjcpu2013-09-26 09:37:00

楼主的分析有些问题:
1 { 如果有朋友认为结果是2的原因,是因为1-(-1)=1+1=2。那么我只能说恭喜你,虽然你的推导与结果相同,但是想法是错误的。因为这里是无符号数,不是有符号数,所以这里没有-1。 }
关于这部分,这个分析并没有错,按照补码的计算,-(-1)相当于对1取了两次补码,所以结果还是1,最终结果是1+1=2没错

2 关于后两个实验的结果的不同,我只看到你的printf使用的格式化参数不同而已,跟计算的数据类型根本没有关系

zhangyd62012-09-21 16:13:51

GFree_Wind: 这个其实很简单。——我个人觉得不算巧妙。

你要明白其实加减运算对于CPU来说,都是一样的,其实都是加法运算。
这也就是为啥,负数要用二进制补码的形式。

那.....
其实我是想讨论一下汇编sub指令对于两个无符号的数是怎么样操作的。我们知道[x-y]补=【x]补 +【-y]补,【[x]补】补=【x]原。 但是对于无符号的数是没有符号的,CPU在做减法的时候怎么处理,是有借位么? 在intel的手册中有如下:
The SUB instruction performs integer subtraction. It evaluates the result for both signed and unsigned integer operands and sets the OF and CF flags to indicate an overflow in the sign