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) }
// a, b; a == b 0xc00005cf57 0xc00005cf57 false // c, d &{} &{} // c, d, c == d 0x118c370 0x118c370 true
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 在逃逸后本质上指向了 zerobase
Analyse 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
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? ". Die VariableDies hängt hauptsächlich mit einem Optimierungsdetail der Go-Laufzeit zusammen, wie folgt:rrreee
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. Konzentrieren Sie sich auf das zweite Detail: „Warum sind die beiden leeren Strukturen vor dem Entkommen nicht gleich?“ ”.Warum sind sie nicht gleich, ohne zu entkommen?
- 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:
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 催更。