是否有必要进行早期检查以确保Golang中写入的安全性?

时间:2016-07-24 05:03:00

标签: go

如果你看"编码/二进制"包裹:

func (littleEndian) Uint64(b []byte) uint64 {
    _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
    return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
        uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
}

func (littleEndian) PutUint64(b []byte, v uint64) {
    _ = b[7] // early bounds check to guarantee safety of writes below
    b[0] = byte(v)
    b[1] = byte(v >> 8)
    b[2] = byte(v >> 16)
    b[3] = byte(v >> 24)
    b[4] = byte(v >> 32)
    b[5] = byte(v >> 40)
    b[6] = byte(v >> 48)
    b[7] = byte(v >> 56)
}

你会看到:

_ = b[7] // early bounds check to guarantee safety of writes below

现在考虑一下这个示例代码A(见注释):

package main

import "fmt"

func main() {
    b := []byte{0, 1, 2, 3, 4, 5, 6}
    var v uint64 = 0x0807060504030201

    b[0] = byte(v)
    b[1] = byte(v >> 8)
    b[2] = byte(v >> 16)
    b[3] = byte(v >> 24)
    b[4] = byte(v >> 32)
    b[5] = byte(v >> 40)
    b[6] = byte(v >> 48)
    b[7] = byte(v >> 56) // panic: runtime error: index out of range

    fmt.Println(b)
}

此示例代码B(见注释):

package main

import "fmt"

func main() {
    b := []byte{0, 1, 2, 3, 4, 5, 6}
    var v uint64 = 0x0807060504030201

    b[7] = byte(v >> 56) // panic: runtime error: index out of range
    b[6] = byte(v >> 48)
    b[5] = byte(v >> 40)
    b[4] = byte(v >> 32)
    b[3] = byte(v >> 24)
    b[2] = byte(v >> 16)
    b[1] = byte(v >> 8)
    b[0] = byte(v)

    fmt.Println(b)
}

示例代码C:

package main

import "fmt"

func main() {
    b := []byte{0, 1, 2, 3, 4, 5, 6}
    var v uint64 = 0x0807060504030201

    _ = b[7] // early bounds check to guarantee safety of writes below

    b[0] = byte(v)
    b[1] = byte(v >> 8)
    b[2] = byte(v >> 16)
    b[3] = byte(v >> 24)
    b[4] = byte(v >> 32)
    b[5] = byte(v >> 40)
    b[6] = byte(v >> 48)
    b[7] = byte(v >> 56)

    fmt.Println(b)
}

所以我有两个问题:
Q1:是否有必要进行早期检查以确保Golang中写入的安全性?
Q2:对于早期检查,保证写入的安全性,样本代码更简洁,性能更优化(速度),样本代码A,B,C或......?

A2:我认为B:因为它简洁并且做了早期检查,不是吗?

2 个答案:

答案 0 :(得分:2)

  

Q1:是否有必要进行早期检查以确保Golang中写入的安全性?

这里的答案是“是和否”。通常,“不”,您通常不必在Go中插入边界检查,因为编译器会为您插入它们(这就是为什么当您尝试访问超出切片长度的内存位置时,示例会出现紧急情况)。但是,如果您正在执行多次写入,例如给出的示例,“是”,则需要插入早期边界检查,例如提供的示例,以确保您没有只有部分写入成功,处于错误状态(或者像在示例B中那样重构,以便第一次写入到最大的数组,确保在任何写入成功之前发生恐慌)。

然而,这不是一个“问题”,因为它是一个通用的bug类。如果您不以任何语言进行边界检查(或者不以最高索引开头,如果它是一种强制执行边界检查自己的语言,如Go),则写入不安全。它也在很大程度上取决于情况;在您发布的标准库的示例中,必须进行用户边界检查。但是,在您发布的第二个示例中,不需要进行用户边界检查,因为代码可以像B一样编写,其中编译器在第一行插入边界检查。

  

Q2:对于早期检查,以确保写入的安全性,样本代码更简洁,性能更优化(速度),示例代码A,B,C或......?

     A2:我觉得B:因为它简洁而且做早期检查,不是吗?

你是对的。在B中,编译器将在第一次写入时插入边界检查,以保护其余写入。因为您使用常量(76,... 0)为切片编制索引,所以编译器可以从其余写入中删除边界检查,因为它可以保证它们是安全的

答案 1 :(得分:1)

关于&#34;写作安全的评论&#34;在这里误导。在开始时放置最高边界检查只是一个优化。如果省略它,行为将不会改变(或变为&#34;不安全&#34;)但是您可能会遭受多个边界检查而不仅仅是一个的性能损失,因为每个后续较高索引的最小必需边界增量。

评论中说&#34;保证写入安全&#34;它只是意味着它将保证编译器后续写入是安全的,而不需要插入更多的边界检查。离开它不会使写入不安全,它只会让编译器插入更多的边界检查。在任何情况下,编译器都不会生成不安全的内存访问。

在代码中插入这个假的早期边界检查是否是一个好主意是有争议的,而不是使用它或重写代码以合法地使用最高索引,如在示例代码B中那样。只要它&#39;明确为什么会出现这种情况(例如,带有明智且无误导性的评论)我会说如果你愿意,可以使用它,并发现它有益。通常,通过手动优化,未来的编译器优化可能会使其冗余或以其他方式改变其有效性。