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

聊聊內存中的Slice操作

開發 前端
操作系統、處理器架構、Golang版本不同,均有可能造成相同的源碼編譯后運行時內存地址、數據結構不同。本文僅保證學習過程中的分析數據在當前環境下的準確有效性。

[[417663]]

本文主要關注 slice 的相關操作:

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

環境

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

聲明

操作系統、處理器架構、Golang版本不同,均有可能造成相同的源碼編譯后運行時內存地址、數據結構不同。

本文僅保證學習過程中的分析數據在當前環境下的準確有效性。

代碼清單

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

深入內存

1. 元素賦值

該操作很簡單,直接通過偏移量定位元素內存并賦值,對應一條機器指令:

圖片

如果如下元素索引超過runtime.slice.cap, 則會panic。

  1. src[13] = 200 
圖片

查看可執行程序,Golang編譯器發現代碼異常之后,直接使用runtime.panicIndex函數調用替換了元素賦值及之后的所有操作,退出程序。

圖片

這很令人好奇:明明編譯時期發現了代碼邏輯錯誤,但并沒有終止編譯過程,而是把它變成一個運行時異常。難道運行時異常更好嗎?

針對這個問題暫時沒有找到合理的答案,只能猜測這是編譯器為了應對各種代碼場景的一個通用編譯處理邏輯,而不是僅僅為了處理本例中的情況。

2. make

使用make關鍵字動態創建 slice。編譯之后make會變成什么指令,視情況而定。

代碼清單中第42行的makeSlice函數編譯之后,對應的機器指令如下:

圖片

可以看到,make關鍵字編譯之后,變成了 runtime.makeslice 函數調用,其實現如下:

  1. func makeslice(et *_type, len, cap int) unsafe.Pointer { 
  2.     // 計算需要分配的內存字節數 
  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.  
  12.     // 直接分配內存 
  13.     return mallocgc(mem, et, true

以上代碼非常簡單,有幾個判斷條件稍微解釋下:

(1)overflow表示元素大小和元素數量的乘積是否溢出,即是否大于64位無符號整數的最大值,肯定是不能大于的;

(2)maxAlloc的值為 0x1000000000000,實際上大多數64位處理器和操作系統的內存可尋址范圍并不是64位,而是不超過48位,這是Golang一個內存分配和校驗邏輯;

(3)len>cap時,Golang編譯器會進行檢查 ,編譯失敗。

另外,在Golang源碼中,有個 runtime.makeslice64 函數,并沒有出現在編譯后的可執行程序中。在 Go 編譯器代碼中看到應該是和32位程序編譯相關。我們更關心64位程序,所以不再深究。

3. copy

代碼清單中第23行的copySlice函數編譯之后,對應的機器指令如下:

圖片

將其翻譯為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) 

仔細閱讀以上指令代碼,確定其邏輯與 runtime.slicecopy 函數相匹配,也就是說copy關鍵字編譯之后變成了runtime.slicecopy函數調用。但是編譯器對runtime.slicecopy函數進行了內聯優化,所以最終并不能看到直接的runtime.slicecopy函數調用。

在Golang中,copy關鍵字可以用于把 string 對象拷貝到[]byte對象中;因為字符串類型還沒有學習到,所以暫時擱置這種特殊情況。

4. make and copy

當make和copy兩個關鍵字一起使用時,又發生了新變化。

代碼清單中第35行的makeSliceCopy函數編譯之后,對應的機器指令如下:

圖片

可以清楚的看到,當make和copy兩個關鍵字一起使用時,被Golang編譯器合并成了 runtime.makeslicecopy 函數調用。該函數源代碼邏輯非常清晰,此處不再贅述。

5. append

代碼清單中第29行的growSlice函數對已經滿的 slice 進行 append 操作。

編譯之后,對應的機器指令如下:

圖片

以上代碼邏輯是:首先進行len(slice)+1和cap(slice)比較,對已經滿的 slice 進行 append 操作時,將觸發底層數組的長度擴增(分配新的數組),將其翻譯為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) 

runtime.growslice 函數的功能是:slice 進行append操作時,如果該slice已經滿了,調用該函數重新分配底層數組進行擴容。

在本例中,

  1. 原 slice 的容量是10,調用runtime.growslice函數之后,容量變為20。
  2. slice元素是 int 類型(element_type_pointer),關于該類型的分析可以閱讀內存中的整數 。

通過以上學習研究,對slice的各種操作有了本質上的了解,相信用起來更加得心應手。

本文轉載自微信公眾號「Golang In Memory」

 

責任編輯:姜華 來源: Golang In Memory
相關推薦

2022-11-28 07:21:53

操作系統內存管理

2021-03-28 13:54:31

操作系統內存管理

2021-02-03 15:12:08

java內存溢出

2021-08-12 14:19:14

Slice數組類型內存

2021-01-07 07:53:10

JavaScript內存管理

2021-12-16 06:52:33

C語言內存分配

2022-11-30 08:19:15

內存分配Go逃逸分析

2021-09-02 11:31:28

二叉搜索樹迭代法公共祖先

2023-11-17 11:40:51

C++內存

2021-10-15 08:57:40

內存容量

2021-11-17 08:11:35

MySQL

2023-11-09 11:56:28

MySQL死鎖

2021-08-31 07:54:24

SQLDblink查詢

2024-04-26 00:00:00

Rust檢查器代碼

2021-07-07 12:01:48

iOS內存對齊

2021-09-10 08:18:31

Go語言字符串

2019-07-11 15:43:44

KVMKSM內存

2021-06-29 07:04:16

Sed常用操作

2020-05-09 13:49:00

內存空間垃圾

2021-10-30 19:56:10

Flutter按鈕 Buttons
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美精品一区二区三区四区 在线 | 午夜精品| 欧美精品福利 | 欧美a在线 | 久久久久电影 | 欧美一区免费 | 99爱在线观看 | 免费观看一级毛片视频 | 欧美亚洲国产一区 | 欧美三级网站 | 精国产品一区二区三区 | 久久精品中文 | 日本一区二区三区四区 | 日本欧美国产在线 | 51ⅴ精品国产91久久久久久 | 射欧美| 久久久久久久久中文字幕 | 韩国毛片一区二区三区 | 日日骚av | 欧美日韩久久 | 日日夜夜精品视频 | a视频在线观看 | 91一区二区 | 国产第1页 | 一区在线播放 | 99热碰 | 国产精品一区二区在线 | 成人激情免费视频 | 国产精品视频免费观看 | aaa国产大片 | 国产精品成人一区二区三区夜夜夜 | 精品永久 | 国产精品特级毛片一区二区三区 | 青青草视频网站 | 国产日韩欧美一区 | 免费激情网站 | 免费色网址 | 午夜影视免费片在线观看 | 自拍偷拍亚洲欧美 | 国产精品久久久久久久久免费软件 | 国产精品久久久久久久久久免费 |