Go编译器的评估对于常量表达式和其他表达式是否不同

时间:2016-09-12 06:58:08

标签: go bit-manipulation bitwise-operators bit-shift evaluation

为什么以下代码无法编译?

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)

}

1 个答案:

答案 0 :(得分:8)

常量和非常量表达式之间的评估存在差异,这是由于常量是精确的:

  

数字常量表示任意精度的精确值且不溢出

类型化常量表达式不能溢出;如果结果不能用它的类型表示,那就是编译时错误(这可以在编译时检测到)。

同样的事情不适用于非常量表达式,因为在编译时无法检测到它(它只能在运行时检测到)。对变量的操作可能会溢出。

在您的第一个示例中, ONE是类型为int的类型化常量。这个常量表达式:

ONE << (unsafe.Sizeof(x)*8 - 1)

常量shift expression,以下情况适用:Spec: Constant expressions:

  

如果常量shift expression的左操作数是无类型常量,则结果为整数常量; 否则它是与左操作数相同类型的常量,必须为integer type

因此,shift表达式的结果必须符合int,因为这是一个常量表达式;但由于它没有,这是一个编译时错误。

在您的第二个示例中 ONE不是常量,它是int类型的变量。因此,此处的移位表达式可能会溢出,从而产生预期的负值。

备注:

如果将第二个示例中的ONE更改为常量而不是变量,则会得到相同的错误(因为初始化程序中的表达式将是常量表达式)。如果您在第一个示例中将ONE更改为变量,则它将无效,因为变量不能用于常量表达式(它必须是常量表达式,因为它初始化常量)。

用于查找最小 - 最大值

的常量表达式

您可以使用以下解决方案生成uintint类型的最大值和最小值:

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的值,其中int的最小值为{{ 1}} {-MaxInt - 1由于-1值而导致。

推理不同的行为

为什么常量表达式没有overflow而非常量表达式溢出?

后者很简单:在大多数其他(编程)语言中都存在溢出。所以这种行为与其他语言一致,并且有其好处。

真正的问题是第一个问题:为什么常量表达式不允许溢出?

Go中的常量不仅仅是类型变量的值:它们表示任意精度的精确值。保持完全这个词,如果你有一个值要分配给类型的常量,允许溢出并分配一个完全不同的值并不能真正实现确切

展望未来,这种类型检查和禁止溢出可以捕获这样的错误:

0

这里发生了什么? type Char byte var c1 Char = 'a' // OK var c2 Char = '世' // Compile-time error: constant 19990 overflows Char 有效,因为c1 Char = 'a''a'常量,runerune的别名,int32的数值为'a'它符合97的有效范围(byte)。

但是0..255会导致编译时错误,因为符文c2 Char = '世'的数值'世'不适合19990。如果允许溢出,您的代码将编译并将byte数值(22)分配给'\x16',但显然这不是您的意图。通过禁止溢出,这个错误很容易被捕获,并且在编译时。

验证结果:

c2

输出(在Go Playground上尝试):

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)

要阅读有关常量及其理念的更多信息,请阅读博文: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