簡(jiǎn)單聊聊Go語(yǔ)言中空結(jié)構(gòu)體和空字符串的特殊之處
在日常的編程過(guò)程中,大家應(yīng)該經(jīng)常能遇到各種”空“吧,比如空指針、空結(jié)構(gòu)體、空字符串……代碼中的這些”空“往往是特例,都有特殊的性質(zhì)。
本文就以 Go 語(yǔ)言為例,一起來(lái)看看空結(jié)構(gòu)體和空字符串在 Go 語(yǔ)言中的特殊之處。
首先是空結(jié)構(gòu)體。
Go 語(yǔ)言中的空結(jié)構(gòu)體
我們先來(lái)運(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 的地址,那通過(guò) u 應(yīng)該也能讀出存儲(chǔ)在 0xc000046730 這個(gè)位置的整數(shù) 10吧。 讓我們來(lái)試一試。
println(*(*int)(unsafe.Pointer(&u)))
果然可以!
下面我們?cè)賮?lái)看看另一種“空”——空字符串。
Go 語(yǔ)言中的空字符串
下面這段代碼會(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
下面就來(lái)分析一下背后的原因(從本節(jié)的標(biāo)題也能猜出吧,八成和title = ""這里的空字符串有關(guān))。
首先,由倒數(shù)第 3 行的 /tmp/sandbox3517788398/prog.go:9 +0x5a 可見(jiàn),導(dǎo)致 panic 的代碼是第 9 行的 fmt.Println(title)。
而 “破案”的線索就在報(bào)錯(cuò)信息的這一行(第 7 行):
fmt.(*fmt).padString(0x425000000041f01c?, {0x0, 0x4})
接下來(lái)我們先找出 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 語(yǔ)言中的定義,
// 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 的空字符串!
這里沒(méi)有寫(xiě)錯(cuò),就是長(zhǎng)度為 4 的空字符串。
既然長(zhǎng)度為 4,那別管空不空,fmt.Println() 就要通過(guò)存在于 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"
}()
}
看似通過(guò) = 一下子就能把字符串賦給變量 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 組“語(yǔ)句”的執(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 的“案子”終于破了。
本文通過(guò)兩個(gè)小例子簡(jiǎn)單介紹了 Go 語(yǔ)言中的“空”,諸位也可以測(cè)試測(cè)試其他語(yǔ)言中的“空”有什么特性。
到此這篇關(guān)于簡(jiǎn)單聊聊Go語(yǔ)言中空結(jié)構(gòu)體和空字符串的特殊之處的文章就介紹到這了,更多相關(guān)Go空結(jié)構(gòu)體和空字符串內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語(yǔ)言實(shí)現(xiàn)字符串base64編碼的方法
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)字符串base64編碼的方法,實(shí)例分析了Go語(yǔ)言操作字符串的技巧及base64編碼的使用技巧,需要的朋友可以參考下2015-03-03
go通過(guò)benchmark對(duì)代碼進(jìn)行性能測(cè)試詳解
在開(kāi)發(fā)中我們要想編寫(xiě)高性能的代碼,或者優(yōu)化代碼的性能時(shí),你首先得知道當(dāng)前代碼的性能,在go中可以使用testing包的benchmark來(lái)做基準(zhǔn)測(cè)試 ,文中有詳細(xì)的代碼示例,感興趣的小伙伴可以參考一下2023-04-04
go?語(yǔ)言爬蟲(chóng)庫(kù)goquery的具體使用
GoQuery是專為Go語(yǔ)言設(shè)計(jì)的一個(gè)強(qiáng)大的HTML解析和查詢庫(kù),本文主要介紹了go語(yǔ)言爬蟲(chóng)庫(kù)goquery的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
go語(yǔ)言goto語(yǔ)句跳轉(zhuǎn)到指定的標(biāo)簽實(shí)現(xiàn)方法
這篇文章主要介紹了go語(yǔ)言goto語(yǔ)句跳轉(zhuǎn)到指定的標(biāo)簽實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(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-07
Golang使用ttl機(jī)制保存內(nèi)存數(shù)據(jù)方法詳解
ttl(time-to-live) 數(shù)據(jù)存活時(shí)間,我們這里指數(shù)據(jù)在內(nèi)存中保存一段時(shí)間,超過(guò)期限則不能被讀取到,與Redis的ttl機(jī)制類似。本文僅實(shí)現(xiàn)ttl部分,不考慮序列化和反序列化2023-03-03
Go語(yǔ)言導(dǎo)出內(nèi)容到Excel的方法
這篇文章主要介紹了Go語(yǔ)言導(dǎo)出內(nèi)容到Excel的方法,涉及Go語(yǔ)言操作excel的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02

