Go 编译器对常量表达式和其他表达式的评估是否不同 [英] Does Go compiler's evaluation differ for constant expression and other expression
问题描述
为什么下面的代码无法编译?
包主进口 (fmt"不安全")无功 x 整数 = 1常量 (一个整数 = 1MIN_INT int = ONE <<(unsafe.Sizeof(x)*8 - 1))功能主(){fmt.Println(MIN_INT)}
我收到一个错误
<块引用>main.go:12: 常量 2147483648 溢出 int
以上说法正确.是的,2147483648 溢出 int(在 32 位架构中).但是移位操作应该产生一个负值,即 -2147483648.
但相同的代码有效,如果我将常量更改为变量并得到预期的输出.
包主进口 (fmt"不安全")无功 x 整数 = 1变量 (一个整数 = 1MIN_INT int = ONE <<(unsafe.Sizeof(x)*8 - 1))功能主(){fmt.Println(MIN_INT)}
由于常量的精确性,常量和非常量表达式之间的计算存在差异:
<块引用>数字常量代表任意精度的精确值并且不会溢出.
类型化的常量表达式不能溢出;如果结果不能用它的类型来表示,那就是编译时错误(这可以在编译时检测到).
同样的事情不适用于非常量表达式,因为这在编译时无法检测到(只能在运行时检测到).对变量的操作可能会溢出.
在您的第一个示例中 ONE
是一个类型为 int
的类型化常量.这个常量表达式:
ONE <<<(unsafe.Sizeof(x)*8 - 1)
<块引用>如果常量移位表达式的左操作数是无类型常量,则结果为整数常量;否则它是与左操作数相同类型的常量,必须是整数类型.
因此移位表达式的结果必须适合int
,因为这是一个常量表达式;但由于它没有,这是一个编译时错误.
在你的第二个例子中 ONE
不是一个常量,它是一个 int
类型的变量.所以这里的移位表达式可能会——也将会——溢出,导致预期的负值.
注意事项:
如果您将第二个示例中的 ONE
更改为常量而不是变量,您会得到相同的错误(因为初始化程序中的表达式将是一个常量表达式).如果您将第一个示例中的 ONE
更改为变量,它将不起作用,因为变量不能在常量表达式中使用(它必须是常量表达式,因为它初始化了一个常量).
求最小值-最大值的常量表达式
您可以使用以下解决方案来生成 uint
和 int
类型的最大值和最小值:
const (MaxUint = ^uint(0)最小单位 = 0MaxInt = int(MaxUint >> 1)MinInt = -MaxInt - 1)功能主(){fmt.Printf("uint: %d..%d
", MinUint, MaxUint)fmt.Printf("int: %d..%d
", MinInt, MaxInt)}
输出(在 Go Playground 上试试):
uint: 0..4294967295整数:-2147483648..2147483647
其背后的逻辑在于规范:常量表达式
<块引用>一元按位补码运算符 ^ 使用的掩码匹配非常量规则:无符号常量的掩码全为 1,有符号和无类型常量的掩码全为 -1.
所以类型常量表达式 ^uint(0)
是 uint
类型并且是 uint
的最大值:它具有所有的位设置为 1
.鉴于使用 2 的补码 表示整数:将其向左移动 1
你会得到 max int
的值,其中的 min int
值为 -MaxInt - 1
(-1
由于 0
值).
不同行为的原因
为什么常量表达式没有溢出,非常量表达式没有溢出?>
后者很容易:在大多数其他(编程)语言中存在溢出.所以这种行为与其他语言是一致的,它有它的好处.
真正的问题是第一个问题:为什么常量表达式不允许溢出?
Go 中的常量不仅仅是类型变量的值:它们代表任意精度的精确值.停留在精确这个词上,如果您有一个值要分配给类型化常量,则允许溢出并分配一个完全不同的值并不能真正实现准确.
展望未来,这种类型检查和禁止溢出可以捕获这样的错误:
type 字符字节var c1 Char = 'a'//OKvar c2 Char = '世'//编译时错误:常量 19990 溢出 Char
这里发生了什么?c1 Char = 'a'
有效是因为 'a'
是一个 rune
常量,而 rune
是 rune
的别名code>int32 和 'a'
的数值 97
适合 byte
的有效范围(即 0..255
).
但是 c2 Char = '世'
会导致编译时错误,因为符文 '世'
具有数值 19990
不适合字节
.如果允许溢出,您的代码将编译并将 22
数值 ('x16'
) 分配给 c2
但显然这不是你的意图.通过禁止溢出,这个错误很容易在编译时被发现.
验证结果:
var c1 Char = 'a'fmt.Printf("%d %q %c
", c1, c1, c1)//var c2 Char = '世'//编译时错误:常量 19990 溢出 Charr := '世'var c2 Char = Char(r)fmt.Printf("%d %q %c
", c2, c2, c2)
输出(在 Go Playground 上试试):
97 'a' a22 'x16'
要阅读有关常量及其哲学的更多信息,请阅读博客文章:Go 博客:常量
还有几个相关和/或有趣的问题(+答案):
Golang:故意 int 溢出
Go 如何对常量进行算术运算?
在go中查找常量的地址
为什么这两个 float64 的值不同?
如何将 float64 数字更改为 uint64以正确的方式?
将 10 的幂写成常量
Why does below code fail to compile?
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)
}
I get an error
main.go:12: constant 2147483648 overflows int
Above statement is correct. Yes, 2147483648 overflows int (In 32 bit architecture). But the shift operation should result in a negative value ie -2147483648.
But the same code works, If I change the constants into variables and I get the expected output.
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)
}
There is a difference in evaluation between constant and non-constant expression that arises from constants being precise:
Numeric constants represent exact values of arbitrary precision and do not overflow.
Typed constant expressions cannot overflow; if the result cannot be represented by its type, it's a compile-time error (this can be detected at compile-time).
The same thing does not apply to non-constant expressions, as this can't be detected at compile-time (it could only be detected at runtime). Operations on variables can overflow.
In your first example ONE
is a typed constant with type int
. This constant expression:
ONE << (unsafe.Sizeof(x)*8 - 1)
Is a constant 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.
So the result of the shift expression must fit into an int
because this is a constant expression; but since it doesn't, it's a compile-time error.
In your second example ONE
is not a constant, it's a variable of type int
. So the shift expression here may –and will– overflow, resulting in the expected negative value.
Notes:
Should you change ONE
in the 2nd example to a constant instead of a variable, you'd get the same error (as the expression in the initializer would be a constant expression). Should you change ONE
to a variable in the first example, it wouldn't work as variables cannot be used in constant expressions (it must be a constant expression because it initializes a constant).
Constant expressions to find min-max values
You may use the following solution which yields the max and min values of uint
and int
types:
const (
MaxUint = ^uint(0)
MinUint = 0
MaxInt = int(MaxUint >> 1)
MinInt = -MaxInt - 1
)
func main() {
fmt.Printf("uint: %d..%d
", MinUint, MaxUint)
fmt.Printf("int: %d..%d
", MinInt, MaxInt)
}
Output (try it on the Go Playground):
uint: 0..4294967295
int: -2147483648..2147483647
The logic behind it lies in the 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.
So the typed constant expression ^uint(0)
is of type uint
and is the max value of uint
: it has all its bits set to 1
. Given that integers are represented using 2's complement: shifting this to the left by 1
you'll get the value of max int
, from which the min int
value is -MaxInt - 1
(-1
due to the 0
value).
Reasoning for the different behavior
Why is there no overflow for constant expressions and overflow for non-constant expressions?
The latter is easy: in most other (programming) languages there is overflow. So this behavior is consistent with other languages and it has its benefits.
The real question is the first: why isn't overflow allowed for constant expressions?
Constants in Go are more than values of typed variables: they represent exact values of arbitrary precision. Staying at the word exact, if you have a value that you want to assign to a typed constant, allowing overflow and assigning a completely different value doesn't really live up to exact.
Going forward, this type checking and disallowing overflow can catch mistakes like this one:
type Char byte
var c1 Char = 'a' // OK
var c2 Char = '世' // Compile-time error: constant 19990 overflows Char
What happens here? c1 Char = 'a'
works because 'a'
is a rune
constant, and rune
is alias for int32
, and 'a'
has numeric value 97
which fits into byte
's valid range (which is 0..255
).
But c2 Char = '世'
results in a compile-time error because the rune '世'
has numeric value 19990
which doesn't fit into a byte
. If overflow would be allowed, your code would compile and assign 22
numeric value ('x16'
) to c2
but obviously this wasn't your intent. By disallowing overflow this mistake is easily caught, and at compile-time.
To verify the results:
var c1 Char = 'a'
fmt.Printf("%d %q %c
", c1, c1, c1)
// var c2 Char = '世' // Compile-time error: constant 19990 overflows Char
r := '世'
var c2 Char = Char(r)
fmt.Printf("%d %q %c
", c2, c2, c2)
Output (try it on the Go Playground):
97 'a' a
22 'x16'
To read more about constants and their philosophy, read the blog post: The Go Blog: Constants
And a couple more questions (+answers) that relate and / or are interesting:
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
这篇关于Go 编译器对常量表达式和其他表达式的评估是否不同的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!