Go编译器对常量表达式和其他表达式的评估是否不同
Does Go compiler's evaluation differ for constant expression and other expression
为什么下面的代码编译失败?
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: constant 2147483648 overflows 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)
}
由于常量的精确性,常量和非常量表达式之间的计算存在差异:
Numeric constants represent exact values of arbitrary precision and do not overflow.
类型化常量表达式不能溢出;如果结果不能用它的类型表示,这是一个编译时错误(这可以在编译时检测到)。
同样的事情不适用于非常量表达式,因为这在编译时无法检测到(只能在运行时检测到)。对变量的操作可能会溢出。
在您的第一个示例中 ONE
是类型为int
的类型常量。这个常量表达式:
ONE << (unsafe.Sizeof(x)*8 - 1)
是常数shift expression, the following applies: Spec: Constant expressions:
If the left operand of a constant shift expression is an untyped constant, the result is an integer constant; otherwise it is a constant of the same type as the left operand, which must be of integer type.
因此移位表达式的结果必须适合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:
The mask used by the unary bitwise complement operator ^ matches the rule for non-constants: the mask is all 1s for unsigned constants and -1 for signed and untyped constants.
因此类型化常量表达式 ^uint(0)
的类型为 uint
,并且是 uint
的最大值:它的所有位都设置为 1
。鉴于整数是使用 2's complement 表示的:将其向左移动 1
,您将获得最大值 int
,其中最小值 int
为 -MaxInt - 1
(-1
由于 0
值)。
不同行为的原因
为什么常量表达式没有overflow,非常量表达式会溢出?
后者很容易:在大多数其他(编程)语言中都存在溢出。所以这种行为与其他语言是一致的,它有它的好处。
真正的问题是第一个:为什么常量表达式不允许溢出?
Go 中的常量不仅仅是类型化变量的值:它们表示任意精度的精确值。留在 exact 这个词上,如果你有一个值要分配给 typed 常量,允许溢出并分配一个完全不同的值不会真的不辜负准确.
展望未来,这种类型检查和禁止溢出可以发现这样的错误:
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'
要了解有关常量及其原理的更多信息,请阅读博客 post:The Go Blog: Constants
还有一些相关和/或有趣的问题(+答案):
为什么下面的代码编译失败?
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: constant 2147483648 overflows 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)
}
由于常量的精确性,常量和非常量表达式之间的计算存在差异:
Numeric constants represent exact values of arbitrary precision and do not overflow.
类型化常量表达式不能溢出;如果结果不能用它的类型表示,这是一个编译时错误(这可以在编译时检测到)。
同样的事情不适用于非常量表达式,因为这在编译时无法检测到(只能在运行时检测到)。对变量的操作可能会溢出。
在您的第一个示例中 ONE
是类型为int
的类型常量。这个常量表达式:
ONE << (unsafe.Sizeof(x)*8 - 1)
是常数shift expression, the following applies: Spec: Constant expressions:
If the left operand of a constant shift expression is an untyped constant, the result is an integer constant; otherwise it is a constant of the same type as the left operand, which must be of integer type.
因此移位表达式的结果必须适合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:
The mask used by the unary bitwise complement operator ^ matches the rule for non-constants: the mask is all 1s for unsigned constants and -1 for signed and untyped constants.
因此类型化常量表达式 ^uint(0)
的类型为 uint
,并且是 uint
的最大值:它的所有位都设置为 1
。鉴于整数是使用 2's complement 表示的:将其向左移动 1
,您将获得最大值 int
,其中最小值 int
为 -MaxInt - 1
(-1
由于 0
值)。
不同行为的原因
为什么常量表达式没有overflow,非常量表达式会溢出?
后者很容易:在大多数其他(编程)语言中都存在溢出。所以这种行为与其他语言是一致的,它有它的好处。
真正的问题是第一个:为什么常量表达式不允许溢出?
Go 中的常量不仅仅是类型化变量的值:它们表示任意精度的精确值。留在 exact 这个词上,如果你有一个值要分配给 typed 常量,允许溢出并分配一个完全不同的值不会真的不辜负准确.
展望未来,这种类型检查和禁止溢出可以发现这样的错误:
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'
要了解有关常量及其原理的更多信息,请阅读博客 post:The Go Blog: Constants
还有一些相关和/或有趣的问题(+答案):