首页 > 运算产生溢出

运算产生溢出

一般我们的整数运算都是基于有限域上的运算,所以会产生溢出,溢出的结果可能不是我们所预期的,那么计算机在设计的时候,对于这种运算为什么不发生警告,
访问野指针,原理上是可行的,但不是我们所预期的,系统会发出警告


对于整数的溢出,CPU 是能够检测出来的,它会将检测结果记录到某个寄存器的某个位上(OF 标志位),这就是它给出的警告。


应为数字运算是使用频率非常高的,如果每次都去进行检查会严重影响性能(比如C语言不检查数组越界,也是同一个原因),实际上这是可以做到的,比如C#语言中checked和unchecked操作符用于整型算术运算时控制当前环境中的溢出检查.


概念纠正

1, 整数分为有符号数和无符号数, 无符号数产生回绕, 有符号数产生溢出.

回绕是指, 无符号数, 0-1会变成最大的数, 如1字节的无符号数会变成255. 而255+1会变成最小数0. 这里的255只是1字节的最大值, 对于2字节为65535. 关于C语言中, 有无符号通过unsigned声明. 但在Java等虚拟机语言是没有无符号数一说的.

溢出表面上表现为两个正数相加会变成负数, 两个负数相加会变成正数. 正数和负数相加是不会溢出的. 这主要因为, 存储在内存中的数, 最高bit位是表现符号的, 但计算机只会执行加法, 即从最低位加, 0+0=0 1+0=0 0+1=1 1+1=0, 最后的情况会进1. 又因为补码的编码方式, 在两正或两负相加时, 可能会改变最高符号位的值.

如上所言, 计算机执行加法其实并不在乎数是无符号还是有符号, 它只知道1+1=0再进1. 有无符号是人类的概念. 但计算机在做每次加法(其实减法也是加法)时, 都会改变一些标志位, 其中某些标志是用来比较大小(cmp的本质是试探性执行减法再检测标志, 也就是说还是加法), 如ZF标志检测相等. 还有些标志, 如进位标志CF可检查无符号数的回绕(回绕时必然进位), 溢出标志OF可检查有符号数的溢出.

再次强调一遍, 每次执行加法运算都会置标志位, 再依据你想要有无符号运算, 再检测CF/OF标志.

2, 整数的溢出就是溢出, 并不存在上/下溢. 只有在浮点数运算中才存在上/下溢的概念. 在这里涉及浮点数的表示格式, 相对来说更为复杂. 每种格式都有其表示的范围, 这里还要分为正负, 自然也有正上溢, 正下溢, 负上溢, 负下溢. 主要是浮点能表示的值是有范围的, 虽然远大于同字节的整数, 类似整数也有范围.

还有种相关的概念, 是关于缓冲区的上/下溢. 上溢是当一个超长的数据进入到缓冲区时,超出部分被写入上级缓冲区,下溢是当一个超长的数据进入到缓冲区时,超出部分被写入下级缓冲区, 都有可能导致一个程序或者操作系统崩溃. 但如果是这种理解就跟你说的整数运算不相关了.

也有人理解, 上溢是大于整数的最大值, 下溢是小于整数的最小值. 但这种理解非常不专业, 太表面了. 因为所谓的大于和小于对有无符号数产生的影响不同. 换句话说, 它同时包含了回绕和溢出两种概念, 但在cpu的标志中, 它俩是分开的.

对于浮点数的运算有专门的硬件, 跟整数运算的ALU不是一个部件. 不确定浮点运算上/下溢是否会置相应标志位以表示.

回答问题

cpu, 运算, 置位.

这就是它的工作机制, 非常简洁, 只作一件事. 对错并不是由cpu来判断的, 它不知道这是不是你想要的. 有很多高效的算法依赖这种简洁机制.

你这里的报错应该是指, 为什么程序在编译时不会警告, 而操作系统在运行时不会警告? 而显然它们都知道运算是否溢出或回绕了(通过检测标志). 通常情况下, 静态类型语言如C, 执行运算时是知道数据有无符号的, 自然也知道是检测CF/OF位.

其实关键是, '我'怎么知道你是不是就想依赖这个特殊的机制. 例如, 要执行一个4次循环:

unsigned char c = 0;
do {
    c += 64;
    printf("%s\n", "I'm boring");
} while (c !=0 )

这里就利用了c在相加4次后回绕成0. 这里只是一个很无聊的示例, 但你不能否认我这样写就是错的, 反而一般人是看不懂这样的程序的.

编译时不违反语法, 运行时不产生中断. 这就是一个好程序, 至于是不是你想要的程序, 计算机并管不着.

如果只是设置警告的话, 我搜索了下, 并没有此选项. 这里要充分考虑到, C语言的一项设计哲学就是简洁, 相信程序员做正确的事. 它的编译器只给出极其有限的警告和错误提示. 况且, 一个熟练的程序员, 可以自己根据需要在程序中设置if来判断自己的运算是否回绕或溢出. 你瞧, 这是自己的事.

至于操作系统给警告就更扯了. 默认情况下, 程序不引起中断, 操作系统才不在乎程序在干什么呢.

不过, 对于缓冲区溢出, 编译器会给出警告, 有可能需要设置一些编译选项. 而操作系统同样不在乎. 程序是程序的事, 操作系统怎么知道你是不是就想它溢出呢. 如, 几乎所有的漏洞利用都是利用缓冲区溢出. 是的微软也阻止不了这些事, 虽然它想阻止, 虽然它也做了很多安全机制, 虽然锅都可以甩到C/Cpp的语言设计和编译器设计上.

当然这一切都是建立在别人写的编译器和操作系统上, 如果这种认定让你不舒服, 就是你自己的事了, 你可以卷起袖子自己干. 你的地盘你做主.

总结

让我们说的更明白些.

并非编译器检测不到回绕或溢出, 而是这个机制很难认定为bug或是feature. 且非常多见. 你可能自己不写, 但你用的很多库函数的内部可能就使用此机制来高效运算. 再加上C语言的设计哲学, 因此不警告是可以理解的. 相信我, C语言有很多问题, 这可能是最不算问题的问题.

至于操作系统, 你可能并没有相关的知识背景. 但你可以大概理解为, 操作系统将CPU时间分片, 依次赋予不同进程. 操作系统唯一的职责是在分片时间到时调度进程. 考虑到两方面, 一是程序执行整数加法运算的次数远多于你源码里写加法的数目, 二是如果编译器没将相关警告代码插入程序, 操作系统要做到整数溢出检查就几乎要, 每执行一条指令(希望你能明白指令和源码的区别)都要进入整数相加溢出中断(并没有此中断). 这几乎, 没法运行.更何况, 在汇编码里, 是看不出有无符号数的.

【热门文章】
【热门文章】