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

Iht*_*kaS 4 evaluation bit-manipulation bit-shift go bitwise-operators

为什么下面的代码无法编译?

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)

}
Run Code Online (Sandbox Code Playgroud)

我收到一个错误

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)

}
Run Code Online (Sandbox Code Playgroud)

icz*_*cza 9

由常量精确引起的常量和非常量表达式之间的评估存在差异:

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

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

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

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

ONE << (unsafe.Sizeof(x)*8 - 1)
Run Code Online (Sandbox Code Playgroud)

是一个常量移位表达式,以下适用:规范:常量表达式:

如果常量移位表达式的左操作数是无类型常量,则结果为整数常量; 否则它是一个与左操作数相同类型的常量,它必须是整数类型.

因此,shift表达式的结果必须符合a,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)
}
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

uint: 0..4294967295
int: -2147483648..2147483647
Run Code Online (Sandbox Code Playgroud)

它背后的逻辑在于Spec:Constant表达式:

一元按位补码运算符^使用的掩码匹配非常量的规则:对于无符号常量,掩码全为1,对于有符号和无类型常量,掩码均为-1.

因此,类型化的常量表达式^uint(0)是类型,uint并且是最大值uint:它的所有位都设置为1.假设使用2的补码表示整数:向左移动1你将得到max的值,intmin int值是-MaxInt - 1(-1由于该0值).

推理不同的行为

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

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

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

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

继续前进,这种类型检查和禁止溢出可以捕获像这样的错误:

type Char byte
var c1 Char = 'a' // OK
var c2 Char = '?' // Compile-time error: constant 19990 overflows Char
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?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)
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

97 'a' a
22 '\x16' 
Run Code Online (Sandbox Code Playgroud)

要阅读有关常量及其理念的更多信息,请阅读博文:The Go Blog:Constants

还有一些相关和/或有趣的问题(+答案):
Golang:on-purpose int overflow
Go如何对常量执行算术?
在go中查找constant的地址
为什么这两个float64具有不同的值?
如何以正确的方式将float64数字更改为uint64?
将10的幂写为紧凑的常数


归档时间:

查看次数:

450 次

最近记录:

6 年,9 月 前