日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
聊聊內(nèi)存中的Slice操作

本文主要關(guān)注 slice 的相關(guān)操作:

創(chuàng)新互聯(lián)公司長(zhǎng)期為1000多家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為麻山企業(yè)提供專業(yè)的成都做網(wǎng)站、網(wǎng)站設(shè)計(jì),麻山網(wǎng)站改版等技術(shù)服務(wù)。擁有10多年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。

  1. 元素賦值(修改)
  2. make
  3. copy
  4. make and copy
  5. append

環(huán)境

 
 
 
 
  1. OS : Ubuntu 20.04.2 LTS; x86_64
  2. Go : go version go1.16.2 linux/amd64

聲明

操作系統(tǒng)、處理器架構(gòu)、Golang版本不同,均有可能造成相同的源碼編譯后運(yùn)行時(shí)內(nèi)存地址、數(shù)據(jù)結(jié)構(gòu)不同。

本文僅保證學(xué)習(xí)過(guò)程中的分析數(shù)據(jù)在當(dāng)前環(huán)境下的準(zhǔn)確有效性。

代碼清單

 
 
 
 
  1. package main
  2. import "fmt"
  3. func main() {
  4.     var src = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  5.     src[3] = 100
  6.     //src[13] = 200
  7.     dst := makeSlice()
  8.     makeSliceCopy(src)
  9.     growSlice(src)
  10.     copySlice(dst, src)
  11.     sliceStringCopy([]byte("hello world"), "hello slice")
  12. }
  13. //go:noinline
  14. func sliceStringCopy(slice []byte, s string) {
  15.     copy(slice, s)
  16.     PrintInterface(string(slice))
  17. }
  18. //go:noinline
  19. func copySlice(dst []int, src []int) {
  20.     copy(dst, src)
  21.     PrintInterface(dst)
  22. }
  23. //go:noinline
  24. func growSlice(slice []int) {
  25.     slice = append(slice, 11)
  26.     PrintInterface(slice)
  27. }
  28. //go:noinline
  29. func makeSliceCopy(array []int) {
  30.     slice := make([]int, 5)
  31.     copy(slice, array)
  32.     PrintInterface(slice)
  33. }
  34. //go:noinline
  35. func makeSlice() []int {
  36.     slice := make([]int, 5)
  37.     //slice := make([]int, 5, 10)
  38.     //slice := make([]int, 10, 5) // "len larger than cap in make(%v)"
  39.     return slice
  40. }
  41. //go:noinline
  42. func PrintInterface(v interface{}) {
  43.     fmt.Println("it =", v)
  44. }

深入內(nèi)存

1. 元素賦值

該操作很簡(jiǎn)單,直接通過(guò)偏移量定位元素內(nèi)存并賦值,對(duì)應(yīng)一條機(jī)器指令:

如果如下元素索引超過(guò)runtime.slice.cap, 則會(huì)panic。

 
 
 
 
  1. src[13] = 200

查看可執(zhí)行程序,Golang編譯器發(fā)現(xiàn)代碼異常之后,直接使用runtime.panicIndex函數(shù)調(diào)用替換了元素賦值及之后的所有操作,退出程序。

這很令人好奇:明明編譯時(shí)期發(fā)現(xiàn)了代碼邏輯錯(cuò)誤,但并沒(méi)有終止編譯過(guò)程,而是把它變成一個(gè)運(yùn)行時(shí)異常。難道運(yùn)行時(shí)異常更好嗎?

針對(duì)這個(gè)問(wèn)題暫時(shí)沒(méi)有找到合理的答案,只能猜測(cè)這是編譯器為了應(yīng)對(duì)各種代碼場(chǎng)景的一個(gè)通用編譯處理邏輯,而不是僅僅為了處理本例中的情況。

2. make

使用make關(guān)鍵字動(dòng)態(tài)創(chuàng)建 slice。編譯之后make會(huì)變成什么指令,視情況而定。

代碼清單中第42行的makeSlice函數(shù)編譯之后,對(duì)應(yīng)的機(jī)器指令如下:

可以看到,make關(guān)鍵字編譯之后,變成了 runtime.makeslice 函數(shù)調(diào)用,其實(shí)現(xiàn)如下:

 
 
 
 
  1. func makeslice(et *_type, len, cap int) unsafe.Pointer {
  2.     // 計(jì)算需要分配的內(nèi)存字節(jié)數(shù)
  3.     mem, overflow := math.MulUintptr(et.size, uintptr(cap))
  4.     if overflow || mem > maxAlloc || len < 0 || len > cap {
  5.         mem, overflow := math.MulUintptr(et.size, uintptr(len))
  6.         if overflow || mem > maxAlloc || len < 0 {
  7.             panicmakeslicelen()
  8.         }
  9.         panicmakeslicecap()
  10.     }
  11.     // 直接分配內(nèi)存
  12.     return mallocgc(mem, et, true)
  13. }

以上代碼非常簡(jiǎn)單,有幾個(gè)判斷條件稍微解釋下:

(1)overflow表示元素大小和元素?cái)?shù)量的乘積是否溢出,即是否大于64位無(wú)符號(hào)整數(shù)的最大值,肯定是不能大于的;

(2)maxAlloc的值為 0x1000000000000,實(shí)際上大多數(shù)64位處理器和操作系統(tǒng)的內(nèi)存可尋址范圍并不是64位,而是不超過(guò)48位,這是Golang一個(gè)內(nèi)存分配和校驗(yàn)邏輯;

(3)len>cap時(shí),Golang編譯器會(huì)進(jìn)行檢查 ,編譯失敗。

另外,在Golang源碼中,有個(gè) runtime.makeslice64 函數(shù),并沒(méi)有出現(xiàn)在編譯后的可執(zhí)行程序中。在 Go 編譯器代碼中看到應(yīng)該是和32位程序編譯相關(guān)。我們更關(guān)心64位程序,所以不再深究。

3. copy

代碼清單中第23行的copySlice函數(shù)編譯之后,對(duì)應(yīng)的機(jī)器指令如下:

將其翻譯為Golang偽代碼,大意如下:

 
 
 
 
  1. func copySlice(dst []int, src []int) {
  2.     n := len(dst)
  3.     if n  > len(src) {
  4.         n = len(src)
  5.     }
  6.     if &dst[0] != &src[0] {
  7.         runtime.memmove(&dst[0], &src[0], len(dst)*8)
  8.     }
  9.     PrintInterface(dst)
  10. }

仔細(xì)閱讀以上指令代碼,確定其邏輯與 runtime.slicecopy 函數(shù)相匹配,也就是說(shuō)copy關(guān)鍵字編譯之后變成了runtime.slicecopy函數(shù)調(diào)用。但是編譯器對(duì)runtime.slicecopy函數(shù)進(jìn)行了內(nèi)聯(lián)優(yōu)化,所以最終并不能看到直接的runtime.slicecopy函數(shù)調(diào)用。

在Golang中,copy關(guān)鍵字可以用于把 string 對(duì)象拷貝到[]byte對(duì)象中;因?yàn)樽址愋瓦€沒(méi)有學(xué)習(xí)到,所以暫時(shí)擱置這種特殊情況。

4. make and copy

當(dāng)make和copy兩個(gè)關(guān)鍵字一起使用時(shí),又發(fā)生了新變化。

代碼清單中第35行的makeSliceCopy函數(shù)編譯之后,對(duì)應(yīng)的機(jī)器指令如下:

可以清楚的看到,當(dāng)make和copy兩個(gè)關(guān)鍵字一起使用時(shí),被Golang編譯器合并成了 runtime.makeslicecopy 函數(shù)調(diào)用。該函數(shù)源代碼邏輯非常清晰,此處不再贅述。

5. append

代碼清單中第29行的growSlice函數(shù)對(duì)已經(jīng)滿的 slice 進(jìn)行 append 操作。

編譯之后,對(duì)應(yīng)的機(jī)器指令如下:

以上代碼邏輯是:首先進(jìn)行l(wèi)en(slice)+1和cap(slice)比較,對(duì)已經(jīng)滿的 slice 進(jìn)行 append 操作時(shí),將觸發(fā)底層數(shù)組的長(zhǎng)度擴(kuò)增(分配新的數(shù)組),將其翻譯為Golang偽代碼,大意如下:

 
 
 
 
  1. func growSlice(slice []int) {
  2.     if len(slice) + 1 > cap(slice) {
  3.         slice = runtime.growslice(element_type_pointer, slice, 11)
  4.     }
  5.     // cap(slice) == 20
  6.     slice[len(slice)] = 17
  7.     PrintInterface(slice)
  8. }

runtime.growslice 函數(shù)的功能是:slice 進(jìn)行append操作時(shí),如果該slice已經(jīng)滿了,調(diào)用該函數(shù)重新分配底層數(shù)組進(jìn)行擴(kuò)容。

在本例中,

  1. 原 slice 的容量是10,調(diào)用runtime.growslice函數(shù)之后,容量變?yōu)?0。
  2. slice元素是 int 類型(element_type_pointer),關(guān)于該類型的分析可以閱讀內(nèi)存中的整數(shù) 。

通過(guò)以上學(xué)習(xí)研究,對(duì)slice的各種操作有了本質(zhì)上的了解,相信用起來(lái)更加得心應(yīng)手。

本文轉(zhuǎn)載自微信公眾號(hào)「Golang In Memory」


網(wǎng)站標(biāo)題:聊聊內(nèi)存中的Slice操作
分享路徑:http://www.5511xx.com/article/cddoojh.html