成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

用 Go struct 不能犯的一個低級錯誤!

開發(fā) 后端
既然我們知道了他是在代碼優(yōu)化階段被優(yōu)化的,那么相對的,知道了原理的我們也可以借助在 go 編譯運(yùn)行時的 gcflags 指令,讓他不優(yōu)化。

大家好,我是煎魚。

前段時間我分享了 《手撕 Go 面試官:Go 結(jié)構(gòu)體是否可以比較,為什么?》的文章,把基本 Go struct 的比較依據(jù)研究了一番。這不,最近有一位讀者,遇到了一個關(guān)于 struct 的新問題,踩到了雷區(qū)。不得解。

大家一起來看看,建議大家在看到代碼例子后先思考一下答案,再往下看。

獨(dú)立思考很重要。

疑惑的例子

其給出的例子一如下:

  1. type People struct {} 
  2.  
  3. func main() { 
  4.  a := &People{} 
  5.  b := &People{} 
  6.  fmt.Println(a == b) 

你認(rèn)為輸出結(jié)果是什么呢?

輸出結(jié)果是:false。

再稍加改造一下,例子二如下:

  1. type People struct {} 
  2.  
  3. func main() { 
  4.  a := &People{} 
  5.  b := &People{} 
  6.  fmt.Printf("%p\n", a) 
  7.  fmt.Printf("%p\n", b) 
  8.  fmt.Println(a == b) 

輸出結(jié)果是:true。

他的問題是 "為什么第一個返回 false 第二個返回 true,是什么原因?qū)е碌?

煎魚進(jìn)一步的精簡這個例子,得到最小示例:

  1. func main() { 
  2.  a := new(struct{}) 
  3.  b := new(struct{}) 
  4.  println(a, b, a == b) 
  5.  
  6.  c := new(struct{}) 
  7.  d := new(struct{}) 
  8.  fmt.Println(c, d) 
  9.  println(c, d, c == d) 

輸出結(jié)果:

  1. // a, b; a == b 
  2. 0xc00005cf57 0xc00005cf57 false 
  3.  
  4. // c, d 
  5. &{} &{} 
  6. // c, d, c == d 
  7. 0x118c370 0x118c370 true 

第一段代碼的結(jié)果是 false,第二段的結(jié)果是 true,且可以看到內(nèi)存地址指向的完全一樣,也就是排除了輸出后變量內(nèi)存指向改變導(dǎo)致的原因。

進(jìn)一步來看,似乎是 fmt.Print 方法導(dǎo)致的,但一個標(biāo)準(zhǔn)庫里的輸出方法,會導(dǎo)致這種奇怪的問題?

問題剖析

如果之前有被這個 “坑” 過,或有看過源碼的同學(xué)。可能能夠快速的意識到,導(dǎo)致這個輸出是逃逸分析所致的結(jié)果。

我們對例子進(jìn)行逃逸分析:

  1. // 源代碼結(jié)構(gòu) 
  2. $ cat -n main.go 
  3.      5 func main() { 
  4.      6  a := new(struct{}) 
  5.      7  b := new(struct{}) 
  6.      8  println(a, b, a == b) 
  7.      9  
  8.     10  c := new(struct{}) 
  9.     11  d := new(struct{}) 
  10.     12  fmt.Println(c, d) 
  11.     13  println(c, d, c == d) 
  12.     14 } 
  13.  
  14. // 進(jìn)行逃逸分析 
  15. $ go run -gcflags="-m -l" main.go 
  16. # command-line-arguments 
  17. ./main.go:6:10: a does not escape 
  18. ./main.go:7:10: b does not escape 
  19. ./main.go:10:10: c escapes to heap 
  20. ./main.go:11:10: d escapes to heap 
  21. ./main.go:12:13: ... argument does not escape 

通過分析可得知變量 a, b 均是分配在棧中,而變量 c, d 分配在堆中。

其關(guān)鍵原因是因?yàn)檎{(diào)用了 fmt.Println 方法,該方法內(nèi)部是涉及到大量的反射相關(guān)方法的調(diào)用,會造成逃逸行為,也就是分配到堆上。

為什么逃逸后相等

關(guān)注第一個細(xì)節(jié),就是 “為什么逃逸后,兩個空 struct 會是相等的?”。

這里主要與 Go runtime 的一個優(yōu)化細(xì)節(jié)有關(guān),如下:

  1. // runtime/malloc.go 
  2. var zerobase uintptr 

變量 zerobase 是所有 0 字節(jié)分配的基礎(chǔ)地址。更進(jìn)一步來講,就是空(0字節(jié))的在進(jìn)行了逃逸分析后,往堆分配的都會指向 zerobase 這一個地址。

所以空 struct 在逃逸后本質(zhì)上指向了 zerobase,其兩者比較就是相等的,返回了 true。

為什么沒逃逸不相等

關(guān)注第二個細(xì)節(jié),就是 “為什么沒逃逸前,兩個空 struct 比較不相等?”。

Go spec

從 Go spec 來看,這是 Go 團(tuán)隊(duì)刻意而為之的設(shè)計,不希望大家依賴這一個來做判斷依據(jù)。如下:

This is an intentional language choice to give implementations flexibility in how they handle pointers to zero-sized objects. If every pointer to a zero-sized object were required to be different, then each allocation of a zero-sized object would have to allocate at least one byte. If every pointer to a zero-sized object were required to be the same, it would be different to handle taking the address of a zero-sized field within a larger struct.

還說了一句很經(jīng)典的,細(xì)品:

Pointers to distinct zero-size variables may or may not be equal.

另外空 struct 在實(shí)際使用中的場景是比較少的,常見的是:

  • 設(shè)置 context,傳遞時作為 key 時用到。
  • 設(shè)置空 struct 業(yè)務(wù)場景中臨時用到。

但業(yè)務(wù)場景的情況下,也大多數(shù)會隨著業(yè)務(wù)發(fā)展而不斷改變,假設(shè)有個遠(yuǎn)古時代的 Go 代碼,依賴了空 struct 的直接判斷,豈不是事故上身?

不可直接依賴

因此 Go 團(tuán)隊(duì)這番操作,與 Go map 的隨機(jī)性如出一轍,避免大家對這類邏輯的直接依賴,是值得思考的。

而在沒逃逸的場景下,兩個空 struct 的比較動作,你以為是真的在比較。實(shí)際上已經(jīng)在代碼優(yōu)化階段被直接優(yōu)化掉,轉(zhuǎn)為了 false。

因此,雖然在代碼上看上去是 == 在做比較,實(shí)際上結(jié)果是 a == b 時就直接轉(zhuǎn)為了 false,比都不需要比了。

你說妙不?

沒逃逸讓他相等

既然我們知道了他是在代碼優(yōu)化階段被優(yōu)化的,那么相對的,知道了原理的我們也可以借助在 go 編譯運(yùn)行時的 gcflags 指令,讓他不優(yōu)化。

在運(yùn)行前面的例子時,執(zhí)行 -gcflags="-N -l" 指令:

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

你看,兩個比較的結(jié)果都是 true 了。

總結(jié)

在今天這篇文章中,我們針對 Go 語言中的空結(jié)構(gòu)體(struct)的比較場景進(jìn)行了進(jìn)一步的補(bǔ)全。經(jīng)過這兩篇文章的洗禮,你會更好的理解 Go 結(jié)構(gòu)體為什么叫既可比較又不可比較了。

而空結(jié)構(gòu)比較的奇妙,主要原因如下:

若逃逸到堆上,空結(jié)構(gòu)體則默認(rèn)分配的是 runtime.zerobase 變量,是專門用于分配到堆上的 0 字節(jié)基礎(chǔ)地址。因此兩個空結(jié)構(gòu)體,都是 runtime.zerobase,一比較當(dāng)然就是 true 了。

若沒有發(fā)生逃逸,也就分配到棧上。在 Go 編譯器的代碼優(yōu)化階段,會對其進(jìn)行優(yōu)化,直接返回 false。并不是傳統(tǒng)意義上的,真的去比較了。

 

不會有人拿來出面試題,不會吧,為什么 Go 結(jié)構(gòu)體說可比較又不可比較?

 

責(zé)任編輯:武曉燕 來源: 腦子進(jìn)煎魚了
相關(guān)推薦

2018-09-29 16:10:02

編程語言Java程序員

2016-12-15 09:39:05

大數(shù)據(jù)錯誤案例

2011-01-06 11:47:08

2016-12-14 13:30:00

大數(shù)據(jù)應(yīng)用場景錯誤

2021-07-26 10:58:07

Chromebook谷歌更新

2016-11-02 12:56:58

Linux新手錯誤

2017-11-02 15:42:32

開發(fā)錯誤代碼

2023-05-10 08:05:41

GoWeb應(yīng)用

2021-06-10 10:40:14

云計算架構(gòu)云遷移云計算

2025-06-10 03:00:00

2022-06-28 10:13:09

Pandas錯誤Python

2021-04-25 08:58:00

Go拍照云盤

2015-07-06 11:28:40

2013-08-20 10:56:08

BashBash編程Bash錯誤

2011-05-31 15:38:37

CSS

2022-05-17 09:32:24

Bash編程Linux

2019-07-05 08:39:39

GoSQL解析器

2015-08-26 10:00:31

獨(dú)立游戲cp錯誤

2021-09-02 08:40:10

程序員錯誤

2024-10-23 11:00:02

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 久久99精品国产自在现线小黄鸭 | av中文字幕在线播放 | 国产一区二区免费 | 国产激情视频在线免费观看 | 国产激情一区二区三区 | 午夜精品视频在线观看 | www.婷婷亚洲基地 | 99久久精品国产一区二区三区 | 精品日本久久久久久久久久 | 夜夜艹天天干 | 正在播放国产精品 | 久久久久久亚洲精品 | 午夜精品久久久久久久久久久久久 | 农夫在线精品视频免费观看 | 一区二区三区日 | 亚欧精品 | 成年人在线观看视频 | 亚洲国产免费 | 国产精品毛片无码 | 日韩在线播放一区 | 欧美一区二区三区在线观看视频 | av网站免费观看 | 视频国产一区 | 午夜久草| 91久久久www播放日本观看 | 91精品久久久久久久久中文字幕 | 天天艹天天干天天 | 天堂av影院| 久久久精品亚洲 | 国产成人免费 | 日韩羞羞| 亚洲精品一区二区三区中文字幕 | 日韩中文字幕一区二区 | 久热9| 精品免费国产一区二区三区四区介绍 | 成人国产综合 | 亚洲国产精品人人爽夜夜爽 | 久久久精彩视频 | 91中文在线观看 | 婷婷综合色 | 国产精品久久久 |