为什么下面的代码无法编译?
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是类型为type的类型常量int。这个常量表达式:
ONE
int
ONE << (unsafe.Sizeof(x)*8 - 1)
是一个常数移位表达式,适用于:Spec:常数表达式:
如果常量移位表达式的左操作数是未类型化的常量,则结果为整数常量;否则,结果为整数。 否则,它是与左操作数相同类型的常量,该常量必须是 整数类型。
因此,shift表达式的结果必须适合,int因为这是一个常量表达式。但是由于没有,这是一个编译时错误。
在第二个示例 ONE中,它不是常量,而是type的变量int。因此,此处的移位表达式可能会并且将溢出,从而导致预期的负值。
笔记:
如果您ONE在第二个示例中将其更改为常量而不是变量,则会得到相同的错误(因为初始化器中的表达式将是常量表达式)。如果ONE在第一个示例中更改为变量,则该变量将不起作用,因为变量不能在常量表达式中使用(它必须是常量表达式,因为它会初始化常量)。
您可以使用以下解决方案,得出uint和int类型的最大值和最小值:
uint
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:常量表达式:
一元按位补数运算符^所使用的掩码与非常数规则匹配:对于无符号常量,掩码均为1,对于有符号和无类型常量,掩码均为-1。
因此,类型化常量表达式^uint(0)是类型,uint并且是的最大值uint:它的所有位都设置为1。假设整数使用2的补码表示:将其向左移动,1将获得max的值int,其中min int值为-MaxInt - 1(-1由于该0值)。
^uint(0)
1
-MaxInt - 1
-1
0
为什么常量表达式没有溢出,非常量表达式没有溢出?
后者很简单:在大多数其他(编程)语言中,都有溢出。因此,这种行为与其他语言一致,并且具有其优势。
真正的问题是第一个:为什么常量表达式不允许溢出?
Go中的常量不仅仅是类型化变量的值:它们表示 任意精度的精确值 。如果您要为一个 类型化的 常量分配一个值,那么请保持“ 精确 ”一词,允许溢出并分配一个完全不同的值实际上并不能达到 精确 。
展望未来,这种类型的检查和禁止溢出可能会捕获如下错误:
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)的数值。
c1 Char = 'a'
'a'
rune
int32
97
byte
0..255
但是会c2 Char = '世'导致编译时错误,因为符文的'世'数值19990不适合byte。如果允许溢出,您的代码将编译并分配22数值('\x16'),c2但显然这不是您的意图。通过禁止溢出,可以很容易地在编译时捕获此错误。
c2 Char = '世'
'世'
19990
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)
97 'a' a 22 '\x16'