GitHub github.com/eddycjy/blog が含まれています皆さんこんにちは、Jianyu です。 少し前に、「Shandou Go インタビュアー: Go の構造は比較できますか? なぜですか?」をシェアしました。 》の記事で、基本的な Go 構造体の比較基礎を学びました。いいえ、最近、読者が構造体に関する新しい問題に遭遇しましたが、解決できませんでした。 一緒に見てみましょう コード例を見て答えを考えてから読み進めることをお勧めします。 独立して考えることが重要です。 疑わしい例例は次のとおりです:
type People struct {} func main() { a := &People{} b := &People{} fmt.Println(a == b) }
type People struct {} func main() { a := &People{} b := &People{} fmt.Printf("%p\n", a) fmt.Printf("%p\n", b) fmt.Println(a == b) }
最初のものが false を返し、2 つ目が true を返すのはなぜですか?その理由は何ですか?この例をさらに単純化して、最小の例を取得します:
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
コードの最初の段落の結果は false、2 番目の段落の結果は true、メモリ アドレスがまったく同じものを指していることがわかります。出力後の変数メモリ ポインタの変更の原因は除外されます。
さらに詳しく調べると、
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 メソッドが呼び出されることであり、これにはリフレクション関連のメソッドへの多数の呼び出しが含まれます。エスケープ動作を引き起こし、またヒープ上に割り当てられます。 なぜエスケープ後に 2 つの空の構造体が等しいのですか?
最初の詳細、「なぜエスケープ後に 2 つの空の構造体が等しいのか」に注目してください。 ?」
これは主に、次のように Go ランタイムの最適化の詳細に関連しています:
// runtime/malloc.go var zerobase uintptr
Variable
zerobase は、すべての 0 バイト割り当てのベース アドレスです。さらに , これは空 (0 バイト) です。エスケープ分析の後、ヒープに割り当てられたものはすべて zerobase
アドレスを指します。したがって、空の構造体は本質的に # エスケープ後の # を指します。 #zerobase
エスケープなしではこれらが等しくない理由
Go の仕様からすると、これは Go チームによる意図的な設計です。私たちは、誰もがこれに依存することを望んでいません。 .次のように:
これは、実装にサイズ 0 のオブジェクトへのポインタを処理する方法の柔軟性を与えるための意図的な言語選択です。サイズ 0 のオブジェクトへのすべてのポインタが必要な場合、サイズがゼロのオブジェクトへのすべてのポインタが同じである必要がある場合、サイズがゼロのオブジェクトのアドレスを取得する処理は異なります。
は、より大きな構造体内のサイズのフィールド。
という非常に古典的な格言も述べています。
##さらに、空の構造体が実際の使用シナリオで使用されることはほとんどありません。一般的なものは次のとおりです:受け渡し時にキーとして使用されるコンテキストを設定します。 Set empty struct はビジネス シナリオで一時的に使用されます。しかし、ビジネス シナリオでは、そのほとんどはビジネスの発展に伴って変化し続けます。 empty struct 直接判断するのは事故ではないでしょうか?
- 直接信頼できない
- したがって、Go チームの操作は Go マップのランダム性とまったく同じです。誰もがこの種のロジックに直接依存するのを避けるためです。
在运行前面的例子时,执行 -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 催更。