为什么golang中的64位溢出没有留下位移?

sta*_*onk 5 bit-manipulation go bitwise-operators

我正在看看A Tour of Go,我对它们的基本类型感到困惑.示例:

MaxInt uint64     = 1<<64 - 1
Run Code Online (Sandbox Code Playgroud)

不应该在无符号的64位整数中向左移动1 64个位置导致溢出(也就是稍微移过MSB一点)?

但是,在将行更改为以下内容之前,编译器不会抱怨:

MaxInt uint64     = 1<<65 - 1

./basic-types.go:5: constant 36893488147419103231 overflows uint64
Run Code Online (Sandbox Code Playgroud)

如果我编写一些代码来迭代不同长度的左移,包括按照上面的例子中的65移动导致编译器barf,我看到两件事:

  1. 它的行为与我预期的一样,因为1<<63在MSB中1可能是uint64

  2. 它不再溢出(嗯?!?!)

码:

package main

import "fmt"

func main() {
    for i := 60; i < 66; i++ {
        var j uint64 = 1 << uint64(i) - 1
        fmt.Printf("%2d | %64b | %#18x\n", i, j, j)

    }
Run Code Online (Sandbox Code Playgroud)

输出:

60 |     111111111111111111111111111111111111111111111111111111111111 |  0xfffffffffffffff
61 |    1111111111111111111111111111111111111111111111111111111111111 | 0x1fffffffffffffff
62 |   11111111111111111111111111111111111111111111111111111111111111 | 0x3fffffffffffffff
63 |  111111111111111111111111111111111111111111111111111111111111111 | 0x7fffffffffffffff
64 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
65 | 1111111111111111111111111111111111111111111111111111111111111111 | 0xffffffffffffffff
Run Code Online (Sandbox Code Playgroud)

abh*_*ink 9

当你写作

1<<64
Run Code Online (Sandbox Code Playgroud)

1上面没有一个int64.这是一个不变的文字.从语言规范:

始终精确计算常量表达式; 中间值和常量本身可能要求精度明显大于语言中任何预先声明的类型所支持的精度.

因此,常量文字在编译时进行评估,并且可能非常大,因为它不是特定类型的语言实现.

实际上下面会出现溢出错误:

var i int64
i = 1<<65 - 1
Run Code Online (Sandbox Code Playgroud)

因为现在常量文字表达式的计算结果大于int64可以包含的值.

在这里阅读更多相关信息.

要了解示例代码的工作原理i = 65,请参阅Golang 规范中的以下规范:

shift表达式中的右操作数必须具有无符号整数类型,或者是可以转换为无符号整数类型的无类型常量.如果非常量移位表达式左操作数是无类型常量,则首先将其转换为如果移位表达式仅由其左操作数替换时将采用的类型.

上面的blod部分涉及您的代码.请考虑以下代码:

a := 66
var j uint64 = 1<<uint64(a) - 1
Run Code Online (Sandbox Code Playgroud)

在移位运算符中,右操作数是一个非常数的exrpession.因此整个换档操作变为非恒定的换档表达式.因此,如上所述,左操作数1被转换为a uint64.

现在,正在进行转换uint64(1),可以根据需要将其转移<<到任意数量的位置.您可以将其移到64位以上,实现将很容易实现.但在这种情况下,持有uint64(1)上述内存的内存将包含全零.

请注意,根据语言规范,此行为与溢出不同.同样,只要右运算符不是常量表达式,语言实现就允许尽可能多的移位.例如,这将起作用:

a := 6666
var j uint64 = 1<<uint64(a) - 1 // non-constant shift expression
Run Code Online (Sandbox Code Playgroud)

这样想吧.早些时候,这1是无关紧要的.它具有任意精度(取决于实现)并且返回整数(所有位).现在,因为它是a uint64,所以只考虑前64位.

这仍然会导致溢出,因为左操作数1是无类型并且可以包含大量位,返回的值太大而不适合uint64:

var j uint64 = 1<<uint64(66) - 1 // overflow. Note that uint64(64)
fmt.Println(j)                   // is typed, but it's still a constant
Run Code Online (Sandbox Code Playgroud)