簡(jiǎn)單聊聊Go語言中空結(jié)構(gòu)體和空字符串的特殊之處
在日常的編程過程中,大家應(yīng)該經(jīng)常能遇到各種”空“吧,比如空指針、空結(jié)構(gòu)體、空字符串……代碼中的這些”空“往往是特例,都有特殊的性質(zhì)。
本文就以 Go 語言為例,一起來看看空結(jié)構(gòu)體和空字符串在 Go 語言中的特殊之處。
首先是空結(jié)構(gòu)體。
Go 語言中的空結(jié)構(gòu)體
我們先來運(yùn)行這樣一段代碼。
// https://go.dev/play/p/L2YxOr8k6Qq package main type U struct{} type V struct{} func main() { var i = 10 var u = U{} var v = V{} println("i address =", &i) println("u address =", &u) println("v address =", &v) } // 運(yùn)行結(jié)果 // i address = 0xc000046730 // u address = 0xc000046730 // v address = 0xc000046730
i
、u
、v
這 3 個(gè)變量的內(nèi)存地址竟然完全一樣!
u
和 v
的內(nèi)存地址相同就已經(jīng)有點(diǎn)出乎意料了,畢竟它們的類型不同,一個(gè)是 struct U
的實(shí)例(值),一個(gè)是 struct V
的實(shí)例。但更出乎意料的是,這個(gè)內(nèi)存地址竟然還是變量 i
的地址(如下圖)。
這是因?yàn)?struct U
和 struct V
都是空結(jié)構(gòu)體這種特殊的結(jié)構(gòu)體,而空結(jié)構(gòu)體的實(shí)例,即 struct{}{}
,不占用任何存儲(chǔ)空間,圖中自然也就找不到存儲(chǔ)著 struct{}{}
的空間。
不占用存儲(chǔ)空間且內(nèi)存地址相同,這就是空結(jié)構(gòu)體這種“空”的特點(diǎn)。
更有意思的是,既然 u
(或 v
)的地址就是變量 i
的地址,那通過 u
應(yīng)該也能讀出存儲(chǔ)在 0xc000046730
這個(gè)位置的整數(shù) 10
吧。 讓我們來試一試。
println(*(*int)(unsafe.Pointer(&u)))
果然可以!
下面我們?cè)賮砜纯戳硪环N“空”——空字符串。
Go 語言中的空字符串
下面這段代碼會(huì)輸出什么呢?交替出現(xiàn)的 Sora
和空行嗎?
// https://go.dev/play/p/c1ZfChdH0rT package main import "fmt" func main() { title := "" go func() { for { fmt.Println(title) } }() for { go func() { title = "" }() go func() { title = "Sora" }() } }
竟然 painc 了,意不意外?
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x462cca]
goroutine 18 [running]:
fmt.(*buffer).writeString(...)
/usr/local/go-faketime/src/fmt/print.go:108
fmt.(*fmt).padString(0x425000000041f01c?, {0x0, 0x4})
/usr/local/go-faketime/src/fmt/format.go:110 +0x24a
...
fmt.Println(...)
/usr/local/go-faketime/src/fmt/print.go:314
main.main.func1()
/tmp/sandbox3517788398/prog.go:9 +0x5a
created by main.main in goroutine 1
/tmp/sandbox3517788398/prog.go:7 +0x66
下面就來分析一下背后的原因(從本節(jié)的標(biāo)題也能猜出吧,八成和title = ""
這里的空字符串有關(guān))。
首先,由倒數(shù)第 3 行的 /tmp/sandbox3517788398/prog.go:9 +0x5a
可見,導(dǎo)致 panic 的代碼是第 9 行的 fmt.Println(title)
。
而 “破案”的線索就在報(bào)錯(cuò)信息的這一行(第 7 行):
fmt.(*fmt).padString(0x425000000041f01c?, {0x0, 0x4})
接下來我們先找出 fmt.padString()
函數(shù)的定義。
// https://github.com/golang/go/blob/master/src/fmt/format.go#L110 // padString appends s to f.buf, ... func (f *fmt) padString(s string) {
對(duì)照著定義,可以猜出 {0x0, 0x4}
對(duì)應(yīng)的正是參數(shù) s string
。
那再結(jié)合字符串類型 string
在 Go 語言中的定義,
// https://github.com/golang/go/blob/master/src/internal/unsafeheader/unsafeheader.go#L34 type String struct { Data unsafe.Pointer Len int }
不難推測(cè)出,這里相當(dāng)于我們將 String{Data: 0x0, Len: 0x4}
這樣一個(gè)表示字符串的結(jié)構(gòu)體傳遞給了 fmt.padString()
。而這是一個(gè)長(zhǎng)度為 4 的空字符串!
這里沒有寫錯(cuò),就是長(zhǎng)度為 4 的空字符串。
既然長(zhǎng)度為 4,那別管空不空,fmt.Println()
就要通過存在于 Data
中的指針(地址)取出這“4個(gè)字符”——計(jì)算機(jī)就是這么“誠(chéng)實(shí)”。但 Data == 0x0
,是空指針,當(dāng)然就空指針 panic 了,即報(bào)錯(cuò)信息中的“invalid memory address or nil pointer dereference”。
“案子”是破了,可“長(zhǎng)度為 4 的空字符串”又是怎么產(chǎn)生的呢?
罪魁禍?zhǔn)拙驮谶@一對(duì)兒協(xié)程上,
for { go func() { title = "" }() go func() { title = "Sora" }() }
看似通過 =
一下子就能把字符串賦給變量 title
,但實(shí)際上不得不依次對(duì) Data
和 Len
賦值,比如,
go routine1: title.Data = <空字符串""的地址> = 0x0
go routine1: title.Len = <空字符串""的長(zhǎng)度> = 0
go routine2: title.Data = <字符串"Sora"的地址>
go routine2: title.Len = <字符串"Sora"的長(zhǎng)度> = 4
而當(dāng)這一對(duì)兒協(xié)程并發(fā)執(zhí)行時(shí),以上 2 組“語句”的執(zhí)行順序是不確定的,完全有可能出現(xiàn)以下二者交替執(zhí)行情況:
go routine2: title.Data = <字符串"Sora"的地址>
go routine1: title.Data = <空字符串""的地址> = 0x0
go routine1: title.Len = <空字符串""的長(zhǎng)度> = 0
go routine2: title.Len = <字符串"Sora"的長(zhǎng)度> = 4
于是導(dǎo)致了{Data: 0x0, Len: 0x4}
,即長(zhǎng)度為 4 的空字符串。
painc 的“案子”終于破了。
本文通過兩個(gè)小例子簡(jiǎn)單介紹了 Go 語言中的“空”,諸位也可以測(cè)試測(cè)試其他語言中的“空”有什么特性。
到此這篇關(guān)于簡(jiǎn)單聊聊Go語言中空結(jié)構(gòu)體和空字符串的特殊之處的文章就介紹到這了,更多相關(guān)Go空結(jié)構(gòu)體和空字符串內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語言實(shí)現(xiàn)字符串base64編碼的方法
這篇文章主要介紹了go語言實(shí)現(xiàn)字符串base64編碼的方法,實(shí)例分析了Go語言操作字符串的技巧及base64編碼的使用技巧,需要的朋友可以參考下2015-03-03go通過benchmark對(duì)代碼進(jìn)行性能測(cè)試詳解
在開發(fā)中我們要想編寫高性能的代碼,或者優(yōu)化代碼的性能時(shí),你首先得知道當(dāng)前代碼的性能,在go中可以使用testing包的benchmark來做基準(zhǔn)測(cè)試 ,文中有詳細(xì)的代碼示例,感興趣的小伙伴可以參考一下2023-04-04go語言goto語句跳轉(zhuǎn)到指定的標(biāo)簽實(shí)現(xiàn)方法
這篇文章主要介紹了go語言goto語句跳轉(zhuǎn)到指定的標(biāo)簽實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05探索Golang?Redis實(shí)現(xiàn)發(fā)布訂閱功能實(shí)例
這篇文章主要介紹了Golang?Redis發(fā)布訂閱功能實(shí)例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01深入了解Golang網(wǎng)絡(luò)編程N(yùn)et包的使用
net包主要是增加?context?控制,封裝了一些不同的連接類型以及DNS?查找等等,同時(shí)在有需要的地方引入?goroutine?提高處理效率。本文主要和大家分享下在Go中網(wǎng)絡(luò)編程的實(shí)現(xiàn),需要的可以參考一下2022-07-07Golang使用ttl機(jī)制保存內(nèi)存數(shù)據(jù)方法詳解
ttl(time-to-live) 數(shù)據(jù)存活時(shí)間,我們這里指數(shù)據(jù)在內(nèi)存中保存一段時(shí)間,超過期限則不能被讀取到,與Redis的ttl機(jī)制類似。本文僅實(shí)現(xiàn)ttl部分,不考慮序列化和反序列化2023-03-03