2016-07-24 3 views
2

Wenn Sie bei "encoding/binary" Paket aussehen:ist es notwendig, zu früh Grenzen zu überprüfen, um die Sicherheit der Schreibvorgänge in Golang zu garantieren?

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) 
} 

Sie werden sehen:

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

nun diesen Beispielcode A betrachten (siehe Kommentar):

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) 
} 

Und Dieser Beispielcode B (siehe Kommentar):

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) 
} 

Und Beispielcode 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) 
} 

So habe ich zwei Fragen:
Q1: Ist es notwendig, frühzeitig Überprüfung von Grenzen in Golang Sicherheit schreibt zu gewährleisten?
Q2: Für frühe Grenzen zu überprüfen, um die Sicherheit der Schreibvorgänge zu gewährleisten, die Beispielcode ist prägnanter und leistungsoptimiert (Geschwindigkeit), Beispielcode A, B, C oder ...?

A2: Ich denke B: weil es prägnant ist und frühe Grenzen überprüfen, nicht wahr?

Antwort

0

Q1: ist es notwendig, frühe Grenzen zu überprüfen, um die Sicherheit der Schreibvorgänge in Golang zu garantieren?

Die Antwort hier ist "Ja und Nein". Im Allgemeinen, "Nein", müssen Sie in Go normalerweise keine Begrenzungsüberprüfungen einfügen, da der Compiler sie für Sie einfügt (deshalb geraten Ihre Beispiele in Panik, wenn Sie versuchen, auf einen Speicherort jenseits der Länge des Segments zuzugreifen). Wenn Sie jedoch mehrere Schreibvorgänge ausführen, wie im Beispiel "yes", müssen Sie wie im obigen Beispiel eine Überprüfung für frühe Grenzen einfügen, um sicherzustellen, dass Sie nicht nur einige der Schreibvorgänge erfolgreich ausführen in einem schlechten Zustand (oder refactor wie in Beispiel B, so dass der erste Schreibvorgang auf das größte Array erfolgt, um sicherzustellen, dass die Panik auftritt, bevor Schreibvorgänge erfolgreich ausgeführt werden können).

Dies ist jedoch nicht so sehr ein "Go-Problem", wie es eine generische Klasse von Bug ist. Wenn Sie die Grenzen nicht überprüfen (oder nicht mit dem höchsten Index beginnen, wenn es eine Sprache ist, die Begrenzungen erzwingt, die sich selbst wie Go prüft), sind die Schreibvorgänge in keiner Sprache sicher. Es hängt auch sehr stark von der Situation ab; In dem Beispiel aus der von Ihnen geposteten Standardbibliothek ist die Überprüfung der Benutzergrenzen erforderlich. Im zweiten Beispiel, das Sie gepostet haben, ist die Überprüfung der Benutzergrenzen jedoch nicht erforderlich, da der Code wie B geschrieben werden kann, wobei der Compiler die Überprüfung der Grenzen in die erste Zeile einfügt.

Q2: für frühe Schranken überprüfen, um die Sicherheit der Schreibvorgänge zu garantieren, welche Beispiel Code ist prägnanter und leistungsoptimiert (Geschwindigkeit), Beispielcode A, B, C oder ...?

A2: Ich denke B: weil es prägnant ist und frühe Grenzen überprüfen, nicht wahr?

Sie haben Recht. In B wird der Compiler eine Schrankenprüfung für den ersten Schreibvorgang einfügen, um die restlichen Schreibvorgänge zu schützen. Da Sie den Slice mit einer Konstante indizieren (7, 6, ... 0) kann der Compiler die Überprüfung der Grenzen von den restlichen Schreibvorgängen abhängig machen, da er garantieren kann, dass sie sicher sind.

+2

Nein, die Überprüfung der frühen Grenzen ist für die Speichersicherheit nicht erforderlich. Diese Überprüfung der Anfangsbegrenzung dient nur dazu, dass ein Compiler-Hinweis in der Lage ist, weitere Begrenzungsüberprüfungen zu vermeiden (und es gab einige Diskussionen, ob dies überhaupt ein ermutigtes Muster sein sollte). Alle Indexvorgänge sind speicherfrei, es sei denn, Sie verwenden das unsichere Paket. – JimB

+0

Das habe ich im ersten Satz gesagt; Der Compiler fügt in vielen Fällen die Schrankenprüfung für Sie ein und Sie brauchen nicht immer eine Überprüfung der frühen Schranken. Tut mir leid, wenn das unklar war; Ich werde es überarbeiten. –

+0

Hoffentlich ist das klarer. –

1

Der Kommentar über "Sicherheit der Schreibvorgänge" ist hier irreführend. Die Überprüfung der höchsten Grenzen am Anfang ist einfach eine Optimierung. Wenn Sie es weglassen, wird sich das Verhalten nicht ändern (oder "unsicher" werden), aber Sie können die Leistungseinbuße mehrerer Begrenzungsüberprüfungen erleiden, statt nur einer, da die erforderliche Mindestgrenze mit jedem nachfolgenden höheren Index zunimmt.

Wenn der Kommentar sagt "Garantie der Sicherheit von Schreibvorgängen" bedeutet es nur, dass es dem Compiler garantiert, dass die nachfolgenden Schreibvorgänge sicher sein werden, ohne dass mehr Schrankenprüfungen eingefügt werden müssen. Wenn Sie sie nicht angeben, werden die Schreibvorgänge nicht unsicher, und der Compiler fügt nur mehr Begrenzungsüberprüfungen ein. Unter keinen Umständen generiert der Compiler unsichere Speicherzugriffe. Es ist fraglich, ob das Einfügen dieser gefälschten Early-Bounds-Prüfung eine gute Idee im Code ist, anstatt sie zu verwenden oder den Code neu zu schreiben, um legitim den höchsten Index wie in Beispielcode B zu verwenden. So lange es klar ist, warum es ist da (zB mit einem vernünftigen und nicht-irreführenden Kommentar) Ich würde sagen, benutze es wenn du willst und finde es nützlich. Im Allgemeinen besteht bei manuellen Optimierungen die Möglichkeit, dass eine zukünftige Compiler-Optimierung sie überflüssig macht oder anderweitig ihre Effektivität ändert.