Heim  >  Artikel  >  Backend-Entwicklung  >  Sehen! Ein dummer Fehler, den Sie bei der Verwendung von Go struct nicht machen dürfen!

Sehen! Ein dummer Fehler, den Sie bei der Verwendung von Go struct nicht machen dürfen!

藏色散人
藏色散人nach vorne
2021-06-19 15:53:081785Durchsuche

Die folgende Tutorial-Kolumne von golang stellt Ihnen einen einfachen Fehler vor, der bei der Verwendung von Go struct nicht gemacht werden kann. Ich hoffe, dass er Freunden in Not hilfreich sein wird!

Ein dummer Fehler, den Sie bei der Verwendung von Go struct nicht machen dürfen!

Dieser Artikel GitHub github.com/eddycjy/blog wurde aufgenommen

Hallo zusammen, ich bin Jianyu.

Vor einiger Zeit habe ich „Shandou Go Interviewer: Können Go-Strukturen verglichen werden? Warum?“ geteilt. 》 Artikel, untersuchte die Vergleichsbasis der grundlegenden Go-Struktur. Nein, kürzlich stieß ein Leser auf ein neues Problem mit der Struktur, das er nicht lösen konnte.

Lassen Sie uns gemeinsam einen Blick darauf werfen. Es wird empfohlen, dass Sie über die Antwort nachdenken, nachdem Sie das Codebeispiel gesehen haben, bevor Sie unten lesen.

Unabhängiges Denken ist wichtig.

Beispiel für Verwirrung

Das angegebene Beispiel lautet wie folgt:

type People struct {}

func main() {
 a := &People{}
 b := &People{}
 fmt.Println(a == b)
}

Was ist Ihrer Meinung nach das Ausgabeergebnis?

Das Ausgabeergebnis ist: falsch.

Eine kleine Modifikation, Beispiel 2 lautet wie folgt:

type People struct {}

func main() {
 a := &People{}
 b := &People{}
 fmt.Printf("%p\n", a)
 fmt.Printf("%p\n", b)
 fmt.Println(a == b)
}

Das Ausgabeergebnis ist: true.

Seine Frage lautet: „Warum gibt das erste falsch und das zweite wahr zurück? Was ist der Grund?

Fried Fish optimiert dieses Beispiel weiter und erhält das Minimalbeispiel:

func main() {
    a := new(struct{})
    b := new(struct{})
    println(a, b, a == b)

    c := new(struct{})
    d := new(struct{})
    fmt.Println(c, d)
    println(c, d, c == d)
}
Ausgabeergebnis:

// a, b; a == b
0xc00005cf57 0xc00005cf57 false

// c, d
&{} &{}
// c, d, c == d
0x118c370 0x118c370 true
Das Das Ergebnis des ersten Teils des Codes ist falsch und das Ergebnis des zweiten Teils ist wahr. Sie können sehen, dass die Speicheradresse auf dasselbe zeigt, was bedeutet, dass der Grund für die Änderung des variablen Speicherzeigers nach der Ausgabe festgelegt ist out

Wenn Sie weiter schauen, scheint es eine fmt.Print-Methode zu sein, aber eine Ausgabemethode in der Standardbibliothek wird dieses seltsame Problem verursachen

fmt.Print 方法导致的,但一个标准库里的输出方法,会导致这种奇怪的问题?

问题剖析

如果之前有被这个 “坑” 过,或有看过源码的同学。可能能够快速的意识到,导致这个输出是逃逸分析所致的结果。

我们对例子进行逃逸分析:

// 源代码结构
$ cat -n main.go
     5    func main() {
     6        a := new(struct{})
     7        b := new(struct{})
     8        println(a, b, a == b)
     9    
    10        c := new(struct{})
    11        d := new(struct{})
    12        fmt.Println(c, d)
    13        println(c, d, c == d)
    14    }

// 进行逃逸分析
$ go run -gcflags="-m -l" main.go
# command-line-arguments
./main.go:6:10: a does not escape
./main.go:7:10: b does not escape
./main.go:10:10: c escapes to heap
./main.go:11:10: d escapes to heap
./main.go:12:13: ... argument does not escape

通过分析可得知变量 a, b 均是分配在栈中,而变量 c, d 分配在堆中。

其关键原因是因为调用了 fmt.Println 方法,该方法内部是涉及到大量的反射相关方法的调用,会造成逃逸行为,也就是分配到堆上。

为什么逃逸后相等

关注第一个细节,就是 “为什么逃逸后,两个空 struct 会是相等的?”。

这里主要与 Go runtime 的一个优化细节有关,如下:

// runtime/malloc.go
var zerobase uintptr

变量 zerobase 是所有 0 字节分配的基础地址。更进一步来讲,就是空(0字节)的在进行了逃逸分析后,往堆分配的都会指向 zerobase 这一个地址。

所以空 struct 在逃逸后本质上指向了 zerobaseAnalyse des Problems

Wenn Sie " gefangen“ oder haben Sie es schon einmal gesehen? Schüler, die den Quellcode gelesen haben, können möglicherweise schnell erkennen, dass diese Ausgabe das Ergebnis einer

Escape-Analyse

ist. Wir führen eine Escape-Analyse am Beispiel durch:

$ go run -gcflags="-N -l" main.go 
0xc000092f06 0xc000092f06 true
&{} &{}
0x118c370 0x118c370 true
Go specWir Aus der Analyse können Sie erkennen, dass die Variablen a und b im Stapel zugewiesen sind, während die Variablen c und d im Heap zugewiesen sind.

Der Hauptgrund dafür ist, dass die Methode fmt.Println aufgerufen wird Dies beinhaltet eine große Anzahl reflexionsbezogener Methodenaufrufe, die ein Escape-Verhalten verursachen, d. h. eine Zuordnung zum Heap.

Warum sind sie nach dem Escape gleich? Strukturen nach dem Entkommen gleich? ".
Dies hängt hauptsächlich mit einem Optimierungsdetail der Go-Laufzeit zusammen, wie folgt:
rrreee
Die Variable zerobase ist die Basisadresse aller 0-Byte-Zuweisungen. Darüber hinaus ist sie leer (0 Bytes) nach der Escape-Analyse zeigen alle Zuweisungen zum Heap auf die Adresse zerobase

Die leere Struktur zeigt also im Wesentlichen auf zerobase nach dem Escape. Die beiden sind gleich, wenn verglichen und true wird zurückgegeben.
Warum sind sie nicht gleich, ohne zu entkommen?
Konzentrieren Sie sich auf das zweite Detail: „Warum sind die beiden leeren Strukturen vor dem Entkommen nicht gleich?“ ”.

  • Aus der Go-Spezifikation geht hervor, dass dies ein bewusster Entwurf des Go-Teams ist, und wir möchten nicht, dass sich jeder darauf als Grundlage für sein Urteil verlässt:

Dies ist ein Absichtliche Sprachauswahl, um Implementierungen Flexibilität bei der Handhabung von Zeigern auf Objekte der Größe Null zu geben. Wenn jeder Zeiger auf ein Objekt der Größe Null unterschiedlich sein müsste, müsste jede Zuweisung eines Objekts der Größe Null mindestens ein Byte zuweisen . Wenn jeder Zeiger auf ein Objekt der Größe Null gleich sein müsste, wäre es anders, die Adresse eines Felds der Größe Null innerhalb einer größeren Struktur zu handhaben.

sagte auch ein sehr klassisches, feines Produkt:

Zeiger auf unterschiedliche Variablen der Größe Null können gleich sein oder auch nicht.

Außerdem ist die tatsächliche Verwendung einer leeren Struktur relativ selten. Die häufigsten sind:

Sie wird bei der Übergabe als Schlüssel verwendet. Das Festlegen einer leeren Struktur wird vorübergehend in Geschäftsszenarien verwendet.

🎜Angenommen, es gibt einen alten Go-Code, der auf der direkten Beurteilung einer leeren Struktur basiert Ist das nicht ein Zufall? der Logik 🎜🎜In dem Szenario, in dem es kein Entrinnen gibt, wird die Vergleichsaktion zweier leerer Strukturen tatsächlich während der Codeoptimierungsphase optimiert und daher falsch Es sieht so aus, als würde == im Code verglichen. Wenn a == b, wird es tatsächlich direkt in false konvertiert, und es besteht keine Notwendigkeit, es zu vergleichen. 🎜🎜Das gibt es nicht Escape, um sie gleich zu machen🎜🎜Da wir nun wissen, dass sie während der Codeoptimierungsphase optimiert werden, können wir, wenn wir das Prinzip kennen, die gcflags-Anweisung auch während der Go-Kompilierung und Laufzeit verwenden, um eine Optimierung zu verhindern. 🎜

在运行前面的例子时,执行 -gcflags="-N -l" 指令:

$ go run -gcflags="-N -l" main.go 
0xc000092f06 0xc000092f06 true
&{} &{}
0x118c370 0x118c370 true

你看,两个比较的结果都是 true 了。

总结

在今天这篇文章中,我们针对 Go 语言中的空结构体(struct)的比较场景进行了进一步的补全。经过这两篇文章的洗礼,你会更好的理解 Go 结构体为什么叫既可比较又不可比较了。

而空结构比较的奇妙,主要原因如下:

  • 若逃逸到堆上,空结构体则默认分配的是 runtime.zerobase 变量,是专门用于分配到堆上的 0 字节基础地址。因此两个空结构体,都是 runtime.zerobase,一比较当然就是 true 了。
  • 若没有发生逃逸,也就分配到栈上。在 Go 编译器的代码优化阶段,会对其进行优化,直接返回 false。并不是传统意义上的,真的去比较了。

不会有人拿来出面试题,不会吧,为什么 Go 结构体说可比较又不可比较?

若有任何疑问欢迎评论区反馈和交流,最好的关系是互相成就,各位的点赞就是煎鱼创作的最大动力,感谢支持。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,回复【000】有我准备的一线大厂面试算法题解和资料;本文 GitHub github.com/eddycjy/blog 已收录,欢迎 Star 催更。

Das obige ist der detaillierte Inhalt vonSehen! Ein dummer Fehler, den Sie bei der Verwendung von Go struct nicht machen dürfen!. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen