Go语言中常见100问题-#18 Neglecting integer overflows
忽略整数溢出
如果不清楚Go语言中如何处理整数溢出可能导致严重的问题,本文首先会介绍一些与整数相关的概念,然后深入分析整数溢出问题。
相关概念
Go语言中总共提供了10种整数类型,其中有四种类型有有符号和无符号之分,如下表所示。
Signed integers | Unsigned integers |
---|---|
int8(8 bits) | uint8(8 bits) |
int16(16 bits) | uint16(16 bits) |
int32(32 bits) | uint32(32 bits) |
int64(64 bits) | uint64(64 bits) |
除了上面的8种类型,还有两种整数类型是:int和uint,这两种类型的大小取决于系统,在32位操作系统上,int/uint类型为32 bits,在64位系统上,int/uint类型为64 bits.
现在开始讨论溢出问题,假设给一个int32类型的变量赋最大值,然后将其自增1,打印输出的结果是多少呢?
var counter int32 = math.MaxInt32
counter++
fmt.Printf("counter=%d\n", counter)
上述代码可以编译通过,并且在运行时没有出现panic,然而,产生了溢出,得到结果如下,是一个负数。
counter=-2147483648
当算术运算创建的值超出了可以用给定字节数表示的范围时,会产生整数溢出。int32类型是32位表示,它的最大值(math.MaxInt32)的二进制表示如下,有31个bit位设置为1,最高的bit位为0:
01111111 11111111 11111111 11111111
因为int32是一个有符号整数,它的最高位(最左边的bit位)是符号位:0表示正数,1表示负数。如果对math.MaxInt32进行增加1操作,没有剩余的bit位表示这个新值。这会导致整数溢出,对应到二进制上,结果如下。
10000000 00000000 00000000 00000000
现在最高位(符号位)为1,表示这是一个负数。此值是32位表示的有符号整数可能的最小值。
「NOTE:可能的最小负数不是 11111111 11111111 11111111 11111111。实际上,大多数系统都依赖于两者的补码运算来表示二进制数(将每一位反转并加1)。这样处理的目的是使得 x+(-x)等于0,不管x是什么。」
var counter int32 = math.MaxInt32 + 1
constant 2147483648 overflows int32
但是在运行时,整数上溢和下溢是无感知的,不会导致应用程序崩溃。所以要对这种行为保持谨慎,它可能会导致潜在的bug(例如整数自增或正整数相加出现负数结果)。
在什么时候需要考虑整数溢出呢?在大多数情况下,比如请求处理计数器或基本的加法和乘法时,如果我们使用了合适正确的类型,不用太关心。但在某些情况下,比如在内存受限的项目中,使用较小整数类型,在处理大数或进行转换时,我们需要检查是否存在溢出问题。
「NOTE:1996年阿丽亚娜火箭5发射失败(https://www.bugsnag.com/blog/bug-day-ariane-5-disaster)是由于将64位浮点数转换为16位有符号整数导致的。」
整数自增的时候检测是否存在溢出
基于定义的变量类型(int8、int16、int32、int64、uint8), 在进行自增操作期间,我们可以根据数学常数检查是否存在溢出,例如对于int32类型,采用如下方法进行检查。
func Inc32(counter int32) int32 {
if counter == math.MaxInt32 {
panic("int32 overflow")
}
return counter + 1
}
由于counter的类型为int32,int32类型的最大值为math.MaxInt32,所以将其与math.MaxInt32进行比较,如果相等,进行自增会溢出,进行panic处理。对于int和uint类型的变量进行自增操作,处理逻辑与之类似。在Go1.17之前,我们需要手动定义一个最大或最小值,将其进行比较。但是现在,这些值已经内置到了math包中,我们可以直接取用math.MaxInt、math.MinInt、math.MaxUint。
func IncInt(counter int) int {
if counter == math.MaxInt {
panic("int overflow")
}
return counter + 1
}
同理对于uint,将其与math.MaxUint进行比较。
func IncUint(counter uint) uint {
if counter == math.MaxUint {
panic("uint overflow")
}
return counter + 1
}
整数相加的时候检测是否存在溢出
两个整数进行相加操作,如何判断是否存在溢出呢?答案是使用math.MaxIn. 如果a大于math.MaxInt-b,则会导致a+b时溢出。
func AddInt(a, b int) int {
if a > math.MaxInt-b {
panic("int overflow")
}
return a + b
}
整数相乘的时候检测是否存在溢出
判断两个整数相乘的结果是否存在溢出有点小复杂,需要检查相乘的整数是否有值为math.MinInt. 如果乘数a或b一个为0,则结果为0.如果a或b有一个为1,则结果为a或b.如果a或b为math.MinInt,则会存在下溢,如果result/b!=a,则说明存在上溢。
func MultiplyInt(a, b int) int {
if a == 0 || b == 0 {
return 0
}
result := a * b
if a == 1 || b == 1 {
return result
}
if a == math.MinInt || b == math.MinInt {
panic("integer overflow")
}
if result/b != a {
panic("integer overflow")
}
return result
}
总结,在Go语言中,当出现上溢或下溢的时候并没有提示,如果我们想检查是否存在溢出避免潜在的错误,可以使用上面代码进行检查。除此之外,Go还提供了一个处理大数据的包:math/big, 在处理大数据的时候,这个包很有用。
相关文章
- Go语言微服务开发框架:Go chassis
- Django框架:11、from组件校验用户数据、渲染标签、展示报错信息、校验参数补充、源码刨析、modelform组件、django中间件
- Django框架:10、Ajax补充说明、多对多三种创建方法、Django内置序列化组件、批量操作数据方法、分页器思路
- Django框架:9、Ajax简介、基本语法、数据编码格式、携带文件数据
- Django框架:8、聚合查询、分组查询、F与Q查询、ORM查询优化、ORM事务操作、ORM常用字段类型、ORM常用字段参数
- Django框架:7、模型层之ORM执行SQL语句、双下划线查询、ORM外键字段的创建、ORM跨表查询
- Django框架:6、模型层之ORM查询关键字、SQL语句转换
- Djiango框架:5、pycharm虚拟环境,视图层之三板斧、JsonResponse对象、request对象、FBV与CBV,模板层之模板语法、模板传值、模板过滤器
- Django框架:4、视图层之路由匹配、转换器、正则匹配、路由反向解析、路由分发、名称空间
- Django框架:3、Django请求生命周期(重要)
- Django框架:2、静态文件配置、form表单、request对象、pycharm链接数据库、django链接数据库、ORM框架
- Django框架:1、手撸web框架、Django框架简介、安装与使用和小白必会三板斧
- 记录在linux上单机elasticsearch8和kibana8
- 《痞子衡嵌入式半月刊》 第 69 期
- 痞子衡嵌入式:对比恩智浦全系列MCU(包含Kinetis/LPC/i.MXRT/MCX)的GPIO电平中断设计差异
- 痞子衡嵌入式:我被邀请做科锐国际旗下数科同道主办的技术沙龙嘉宾
- 痞子衡嵌入式:低功耗&高性能边缘人工智能应用的新答案 - MCXN947
- 《痞子衡嵌入式半月刊》 第 68 期
- 痞子衡嵌入式:我为2021 TencentOS Tiny AIoT应用创新大赛做了场直播培训
- 痞子衡嵌入式:我被邀请做贸泽电子&与非网联合推出的《对话工程师》节目嘉宾