short、int、long 是C语言中常用的三种整数类型,分别称为短整型、整型、长整型。

在现代操作系统中,short、int、long 的长度分别是 2、4、4 或者 8,它们只能存储有限的数值,当数值过大或者过小时,超出的部分会被直接截掉,数值就不能正确存储了,我们将这种现象称为溢出(Overflow)。

溢出的简单理解就是,向木桶里面倒入了过量的水,木桶盛不了了,水就流出来了。

要想知道数值什么时候溢出,就得先知道各种整数类型的取值范围。

无符号数的取值范围

计算无符号数(unsigned 类型)的取值范围(或者说最大值和最小值)很容易,将内存中的所有位(Bit)都置为 1 就是最大值,都置为 0 就是最小值。

以 unsigned char 类型为例,它的长度是 1,占用 8 位的内存,所有位都置为 1 时,它的值为 28 - 1 = 255,所有位都置为 0 时,它的值很显然为 0。由此可得,unsigned char 类型的取值范围是 0~255。

前面我们讲到,char 是一个字符类型,是用来存放字符的,但是它同时也是一个整数类型,也可以用来存放整数,请大家暂时先记住这一点

有读者可能会对 unsigned char 的最大值有疑问,究竟是怎么计算出来的呢?下面我就讲解一下这个小技巧。

将 unsigned char 的所有位都置为 1,它在内存中的表示形式为1111 1111,最直接的计算方法就是:

20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 = 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 = 255

这种“按部就班”的计算方法虽然有效,但是比较麻烦,如果是 8 个字节的 long 类型,那足够你计算半个小时的了。

我们不妨换一种思路,先给 1111 1111 加上 1,然后再减去 1,这样一增一减正好抵消掉,不会影响最终的值。

给 1111 1111 加上 1 的计算过程为:

0B1111 1111 + 0B1 = 0B1 0000 0000 = 28 = 256

可以发现,1111 1111 加上 1  后需要向前进位(向第 9 位进位),剩下的 8 位都变成了 0,这样一来,只有第 9 位会影响到数值的计算,剩下的 8 位对数值都没有影响。第 9 位的权值计算起来非常容易,就是:

29-1 = 28 = 256

然后再减去 1:

28 - 1 = 256 - 1 = 255

加上 1 是为了便于计算,减去 1 是为了还原本来的值;当内存中所有的位都是 1 时,这种“凑整”的技巧非常实用。

按照这种巧妙的方法,我们可以很容易地计算出所有无符号数的取值范围(括号内为假设的长度):

  unsigned char unsigned short unsigned int(4字节) unsigned long(8字节)
最小值 0 0 0 0
最大值 28 - 1 = 255 216 - 1 = 65,535 ≈ 6.5万 232 - 1 = 4,294,967,295 ≈ 42亿 264 - 1 ≈ 1.84×1019

有符号数的取值范围

有符号数以补码的形式存储,计算取值范围也要从补码入手。我们以 char 类型为例,从下表中找出它的取值范围:

补码 反码 原码
1111 1111 1111 1110 1000 0001 -1
1111 1110 1111 1101 1000 0010 -2
1111 1101 1111 1100 1000 0011 -3
…… …… …… ……
1000 0011 1000 0010 1111 1101 -125
1000 0010 1000 0001 1111 1110 -126
1000 0001 1000 0000 1111 1111 -127
1000 0000 -- -- -128
0111 1111 0111 1111 0111 1111 127
0111 1110 0111 1110 0111 1110 126
0111 1101 0111 1101 0111 1101 125
…… …… …… ……
0000 0010 0000 0010 0000 0010 2
0000 0001 0000 0001 0000 0001 1
0000 0000 0000 0000 0000 0000 0

我们按照从大到小的顺序将补码罗列出来,很容易发现最大值和最小值。

淡黄色背景的那一行是我要重点说明的。如果按照传统的由补码计算原码的方法,那么 1000 0000 是无法计算的,因为计算反码时要减去 1,1000 0000 需要向高位借位,而高位是符号位,不能借出去,所以这就很矛盾。

是不是该把 1000 0000 作为无效的补码直接丢弃呢?然而,作为无效值就不如作为特殊值,这样还能多存储一个数字。计算机规定,1000 0000 这个特殊的补码就表示 -128。

为什么偏偏是 -128 而不是其它的数字呢?

首先,-128 使得 char 类型的取值范围保持连贯,中间没有“空隙”。

其次,我们再按照“传统”的方法计算一下 -128 的补码:

  • -128 的数值位的原码是 1000 0000,共八位,而 char 的数值位只有七位,所以最高位的 1 会覆盖符号位,数值位剩下 000 0000。最终,-128 的原码为 1000 0000。
  • 接着很容易计算出反码,为 1111 1111。
  • 反码转换为补码时,数值位要加上 1,变为 1000 0000,而 char 的数值位只有七位,所以最高位的 1 会再次覆盖符号位,数值位剩下 000 0000。最终求得的 -128 的补码是 1000 0000。

-128 从原码转换到补码的过程中,符号位被 1 覆盖了两次,而负数的符号位本来就是 1,被 1 覆盖多少次也不会影响到数字的符号。

你看,虽然从 1000 0000 这个补码推算不出 -128,但是从 -128 却能推算出 1000 0000 这个补码,这么多么的奇妙,-128 这个特殊值选得恰到好处。

负数在存储之前要先转换为补码,“从 -128 推算出补码 1000 0000”这一点非常重要,这意味着 -128 能够正确地转换为补码,或者说能够正确的存储。

关于零值和最小值

仔细观察上表可以发现,在 char 的取值范围内只有一个零值,没有+0-0的区别,并且多存储了一个特殊值,就是 -128,这也是采用补码的另外两个小小的优势。

如果直接采用原码存储,那么0000 00001000 0000将分别表示+0-0,这样在取值范围内就存在两个相同的值,多此一举。另外,虽然最大值没有变,仍然是 127,但是最小值却变了,只能存储到 -127,不能存储 -128 了,因为 -128 的原码为 1000 0000,这个位置已经被-0占用了。

按照上面的方法,我们可以计算出所有有符号数的取值范围(括号内为假设的长度):

  char short int(4个字节) long(8个字节)
最小值 -27 = -128 -215 = -32,768 ≈ -3.2万 -231 = -2,147,483,648 ≈ -21亿 -263 ≈ -9.22×1018
最大值 27 - 1= 127 215 - 1 = 32,767 ≈ 3.2万 231 - 1 = 2,147,483,647 ≈ 21亿 263 - 1≈ 9.22×1018

上节我们还留下了一个疑问,[1000 0000 …… 0000 0000]补这个 int 类型的补码为什么对应的数值是 -231,有了本节对 char 类型的分析,相信聪明的你会举一反三,自己解开这个谜团。

数值溢出

char、short、int、long 的长度是有限的,当数值过大或者过小时,有限的几个字节就不能表示了,就会发生溢出。发生溢出时,输出结果往往会变得奇怪,请看下面的代码:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. unsigned int a = 0x100000000;
  5. int b = 0xffffffff;
  6. printf("a=%u, b=%d\n", a, b);
  7. return 0;
  8. }

运行结果:
a=0, b=-1

变量 a 为 unsigned int 类型,长度为 4 个字节,能表示的最大值为 0xFFFFFFFF,而 0x100000000 = 0xFFFFFFFF + 1,占用33位,已超出 a 所能表示的最大值,所以发生了溢出,导致最高位的 1 被截去,剩下的 32 位都是0。也就是说,a 被存储到内存后就变成了 0,printf 从内存中读取到的也是 0。

变量 b 是 int 类型的有符号数,在内存中以补码的形式存储。0xffffffff 的数值位的原码为 1111 1111 …… 1111 1111,共 32 位,而 int 类型的数值位只有 31 位,所以最高位的 1 会覆盖符号位,数值位只留下 31 个 1,所以 b 的原码为:

1111 1111 …… 1111 1111

这也是 b 在内存中的存储形式。

当 printf 读取到 b 时,由于最高位是 1,所以会被判定为负数,要从补码转换为原码:

[1111 1111 …… 1111 1111]
= [1111 1111 …… 1111 1110]
= [1000 0000 …… 0000 0001]
= -1

最终 b 的输出结果为 -1。

C语言:整数取值范转及溢出的更多相关文章

  1. 从C语言的整数取值范围说开去

    在ILP32中, char, short, int, long, long long, pointer分别占1, 2, 4, 4, 8, 4个字节,在 LP64中, char, short, int, ...

  2. C语言数据类型取值范围

    一.获取数据类型在系统中的位数 在不同的系统中,数据类型的字节数(bytes)不同,位数(bits)也有所不同,那么对应的取值范围也就有了很大的不同,那我们怎么知道你当前的系统中C语言的某个数据类型的 ...

  3. 编写一个js函数,该函数有一个n(数字类型),其返回值是一个数组,该数组内是n个随机且不重复的整数,且整数取值范围是[2,32]

    首先定义个fn用来返回整数的取值范围: function getRand(a,b){ var rand = Math.ceil(Math.random()*(b-a)+a); return rand; ...

  4. C语言数据类型取值范围解析

    版权声明:本文为博主原创文章,未经博主允许不得转载.   为什么int类型的取值范围会是-2^31 ~ 2^31-1  ,为什么要减一呢? 计算机里规定,8位二进制为一个字节,拿byte来说,一个BY ...

  5. 编写一个javscript函数 fn,该函数有一个参数 n(数字类型),其返回值是一个数组,该数组内是 n 个随机且不重复的整数,且整数取值范围是 [2, 32]。

    function fn(n){ if(n<2 || n>32) { return; }  if(!n) { return;}  //判断n是否为数字  if(!/^[0-9]+.?[0-9 ...

  6. 带符号的char类型取值范围为什么是-128——127

    以前经常看到带符号的char类型取值范围是-128——127,今天突然想为什么不是-127——127,-128是怎么来的? 127好理解,char类型是8位,最高位是符号位,0正1负,所以011111 ...

  7. GO语言学习笔记2-int类型的取值范围

    相比于C/C++语言的int类型,GO语言提供了多种int类型可供选择,有int8.int16.int32.int64.int.uint8.uint16.uint32.uint64.uint. 1.i ...

  8. c语言基础表达式, 关系运算符, 逻辑运算符, 位运算符, 数据的取值范围, 分支结构(if...else, switch...case)

    1.表达式: 表达式的判断是有无结果(值), 最简单的表达式是一个常量或变量, 如:12, a, 3 + 1, a + b, a + 5 都是表达式 2.BOOL(布尔)数据类型: c语言中除了基本数 ...

  9. C语言中数据类型取值范围的计算的理解与总结

    c语言中,数据类型有short,int,long,char,float,double,然后除了浮点型只有 有符号数(signed)外,其他的数据类型都分为有符号(signed)和无符号(unsigne ...

随机推荐

  1. javascript获取日期,年月,日

    <SCRIPT LANGUAGE="JavaScript"> var myDate = new Date();     myDate.getYear();       ...

  2. 微信内 H5 页面自定义分享

    起源: 最近公司在做一个活动的h5页面,在微信内打开时需要进行微信授权,然后后端会重定向到这个页面并且携带了一些参数(openid等).问题是点击微信的原生分享时,会把携带的这些参数一起分享出去,等于 ...

  3. AlexeyAB DarkNet YOLOv3框架解析与应用实践(二)

    AlexeyAB DarkNet YOLOv3框架解析与应用实践(二) 版本3有什么新功能? YOLOv3使用了一些技巧来改进训练和提高性能,包括:多尺度预测.更好的主干分类器等等.全部细节都在我们的 ...

  4. 网络模型mAP计算实现代码

    网络模型mAP计算实现代码 一.mAP精度计算 这里首先介绍几个常见的模型评价术语,现在假设我们的分类目标只有两类,计为正例(positive)和负例(negtive)分别是: 1)True posi ...

  5. 自监督学习(Self-Supervised Learning)多篇论文解读(下)

    自监督学习(Self-Supervised Learning)多篇论文解读(下) 之前的研究思路主要是设计各种各样的pretext任务,比如patch相对位置预测.旋转预测.灰度图片上色.视频帧排序等 ...

  6. PyTorch神经网络集成技术

    PyTorch神经网络集成技术 create_python_neuropod 将任意python代码打包为一个neurood包. create_python_neuropod( neuropod_pa ...

  7. 《精通 ASP.NET Core MVC (第七版)》开始发售

    学习 Web 开发技术很难吗?没有适合的学习资料,确实很枯燥,很难.如果有一本如同良师益友的优秀图书辅助,就很轻松,一点也不难! 对于优秀的技术图书来说,必须从读者的角度来编写,而不是从作者的角度来编 ...

  8. java容器学习笔记

    容器 容器的组成 容器有两个接口Map和Collection. collection接口有List类和set类. List类可以分为:Vector.LinkedList.ArrayList.CopyO ...

  9. MySQL的自增ID用完了,怎么办?

    既然这块知识点不清楚,那回头就自己动手实践下. 首先,创建一个最简单的表,只包含一个自增id,并插入一条数据. create table t0(id int unsigned auto_increme ...

  10. 从 Nginx 优秀的核心架构设计,揭秘其为何能支持高并发?

    目录: 1. Nginx的整体架构 2. Nginx的模块化设计 3. Nginx的请求方式处理 4. Nginx事件驱动模型 5. Nginx进程处理模型 写在前面 Nginx 是一个 免费的,开源 ...