ホームページ  >  記事  >  バックエンド開発  >  見て! Go 構造体を使用するときに犯してはならない愚かな間違いです。

見て! Go 構造体を使用するときに犯してはならない愚かな間違いです。

藏色散人
藏色散人転載
2021-06-19 15:53:081761ブラウズ

golang の次のチュートリアル コラムでは、Go 構造体を使用するときに犯してはならない低レベルの間違いを紹介します。困っている友人の役に立てば幸いです。

Go 構造体を使用するときに犯してはならない愚かな間違いです。

この記事
GitHub github.com/eddycjy/blog が含まれています
皆さんこんにちは、Jianyu です。

少し前に、「Shandou Go インタビュアー: Go の構造は比較できますか? なぜですか?」をシェアしました。 》の記事で、基本的な Go 構造体の比較基礎を学びました。いいえ、最近、読者が構造体に関する新しい問題に遭遇しましたが、解決できませんでした。

一緒に見てみましょう コード例を見て答えを考えてから読み進めることをお勧めします。

独立して考えることが重要です。

疑わしい例

例は次のとおりです:

type People struct {}

func main() {
 a := &People{}
 b := &People{}
 fmt.Println(a == b)
}
出力結果は何だと思いますか?

出力結果は false です。

少し変更を加えた例 2 は次のとおりです。

type People struct {}

func main() {
 a := &People{}
 b := &People{}
 fmt.Printf("%p\n", a)
 fmt.Printf("%p\n", b)
 fmt.Println(a == b)
}
出力結果は true です。

彼の質問は「

最初のものが 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

、比較すると 2 つは等しく、true が返されます。

エスケープなしではこれらが等しくない理由

2 番目の詳細、「エスケープ前になぜ等しくないのか」に注目してください。 2 つの空の構造体は等しくありませんか? ”.

Go specGo の仕様からすると、これは Go チームによる意図的な設計です。私たちは、誰もがこれに依存することを望んでいません。 .次のように:

これは、実装にサイズ 0 のオブジェクトへのポインタを処理する方法の柔軟性を与えるための意図的な言語選択です。サイズ 0 のオブジェクトへのすべてのポインタが必要な場合、サイズがゼロのオブジェクトへのすべてのポインタが同じである必要がある場合、サイズがゼロのオブジェクトのアドレスを取得する処理は異なります。

は、より大きな構造体内のサイズのフィールド。

という非常に古典的な格言も述べています。

##さらに、空の構造体が実際の使用シナリオで使用されることはほとんどありません。一般的なものは次のとおりです:
受け渡し時にキーとして使用されるコンテキストを設定します。 Set empty struct はビジネス シナリオで一時的に使用されます。

しかし、ビジネス シナリオでは、そのほとんどはビジネスの発展に伴って変化し続けます。 empty struct 直接判断するのは事故ではないでしょうか?
  • 直接信頼できない
  • したがって、Go チームの操作は Go マップのランダム性とまったく同じです。誰もがこの種のロジックに直接依存するのを避けるためです。
逃げ場のないシナリオでは、2 つの空の構造体の比較アクションは、実際には比較していると考えられます。

したがって、コード内では == が比較されているように見えますが、実際には結果が a == b の場合は、そのまま false に変換されます。そして比較する必要はありません。

素晴らしいと思いませんか?

等価にするためのエスケープはありません

コード最適化フェーズ中に最適化されることがわかりました。一方、原理がわかっていれば、コード最適化フェーズ中に gcflags を使用することもできます。コンパイルとランタイムに進み、最適化を防ぐための指示。

在运行前面的例子时,执行 -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 催更。

以上が見て! Go 構造体を使用するときに犯してはならない愚かな間違いです。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。