为什么下面的代码无法编译?
package main
import (
"fmt"
"unsafe"
)
var x int = 1
const (
ONE int = 1
MIN_INT int = ONE << (unsafe.Sizeof(x)*8 - 1)
)
func main() {
fmt.Println(MIN_INT)
}
我收到一个错误
main.go:12:常量2147483648溢出int
以上陈述是正确的。是的,2147483648溢出int(在32位架构中)。但是班次操作应该导致负值,即-2147483648。
但相同的代码工作,如果我将常量更改为变量,我得到预期的输出。
package main
import (
"fmt"
"unsafe"
)
var x int = 1
var (
ONE int = 1
MIN_INT int = ONE << (unsafe.Sizeof(x)*8 - 1)
)
func main() {
fmt.Println(MIN_INT)
}
由常量精确引起的常量和非常量表达式之间的评估存在差异:
数字常量表示任意精度的精确值,不会溢出。
类型化的常量表达式不能溢出;如果结果不能用它的类型表示,那就是编译时错误(这可以在编译时检测到)。
同样的事情不适用于非常量表达式,因为这在编译时无法检测到(它只能在运行时检测到)。对变量的操作可能会溢出。
在你的第一个例子中,ONE
是一个类型为int
的类型常量。这个常量表达式:
ONE << (unsafe.Sizeof(x)*8 - 1)
是一个恒定的shift expression,以下适用:Spec: Constant expressions:
如果常量shift expression的左操作数是无类型常量,则结果为整数常量;否则它是一个与左操作数相同类型的常量,它必须是integer type。
因此,shift表达式的结果必须符合int
,因为这是一个常量表达式;但由于它没有,这是一个编译时错误。
在你的第二个例子中,ONE
不是常量,它是int
类型的变量。因此,此处的移位表达式可能会溢出,从而产生预期的负值。
笔记:
如果您将第二个示例中的ONE
更改为常量而不是变量,则会得到相同的错误(因为初始化程序中的表达式将是常量表达式)。如果你在第一个例子中将ONE
更改为变量,它就不会起作用,因为变量不能用在常量表达式中(它必须是一个常量表达式,因为它初始化一个常量)。
您可以使用以下解决方案,它产生uint
和int
类型的最大值和最小值:
const (
MaxUint = ^uint(0)
MinUint = 0
MaxInt = int(MaxUint >> 1)
MinInt = -MaxInt - 1
)
func main() {
fmt.Printf("uint: %d..%d\n", MinUint, MaxUint)
fmt.Printf("int: %d..%d\n", MinInt, MaxInt)
}
输出(在Go Playground上试试):
uint: 0..4294967295
int: -2147483648..2147483647
它背后的逻辑在于Spec: Constant expressions:
一元按位补码运算符^使用的掩码匹配非常量的规则:对于无符号常量,掩码全为1,对于有符号和无类型常量,掩码均为-1。
因此,类型常量表达式^uint(0)
的类型为uint
,是uint
的最大值:它的所有位都设置为1
。鉴于使用2's complement表示整数:通过1
将其向左移动,您将得到max int
的值,其中min int
值为-MaxInt - 1
(由于-1
值,0
)。
为什么没有overflow用于常量表达式和非常量表达式的溢出?
后者很简单:在大多数其他(编程)语言中存在溢出。所以这种行为与其他语言一致,并且有其好处。
真正的问题是第一个问题:为什么常量表达式不允许溢出?
Go中的常量不仅仅是类型变量的值:它们表示任意精度的精确值。保持单词exact,如果你有一个你想要分配给类型常量的值,允许溢出并分配一个完全不同的值并不能真正达到精确。
继续前进,这种类型检查和禁止溢出可以捕获像这样的错误:
type Char byte
var c1 Char = 'a' // OK
var c2 Char = '世' // Compile-time error: constant 19990 overflows Char
这里发生了什么? c1 Char = 'a'
的作用是因为'a'
是rune
常数,rune
是int32
的别名,而'a'
的数值97
符合byte
的有效范围(0..255
)。
但c2 Char = '世'
导致编译时错误,因为符文'世'
具有数值19990
,它不适合byte
。如果允许溢出,你的代码将编译并将22
数值('\x16'
)分配给c2
,但显然这不是你的意图。通过禁止溢出,这个错误很容易被捕获,并且在编译时。
要验证结果:
var c1 Char = 'a'
fmt.Printf("%d %q %c\n", c1, c1, c1)
// var c2 Char = '世' // Compile-time error: constant 19990 overflows Char
r := '世'
var c2 Char = Char(r)
fmt.Printf("%d %q %c\n", c2, c2, c2)
输出(在Go Playground上试试):
97 'a' a
22 '\x16'
要阅读有关常量及其哲学的更多信息,请阅读博客文章:The Go Blog: Constants
还有一些相关和/或有趣的问题(+答案): Golang: on-purpose int overflow How does Go perform arithmetic on constants? Find address of constant in go Why do these two float64s have different values? How to change a float64 number to uint64 in a right way? Writing powers of 10 as constants compactly