自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go內(nèi)存中的字符串操作

開發(fā) 前端
內(nèi)存中的字符串類型 詳細(xì)描述了字符串在內(nèi)存中的結(jié)構(gòu)及其類型信息。本文主要研究字符串的各種操作(語(yǔ)法糖),在內(nèi)存中實(shí)際的樣子。

[[423000]]

內(nèi)存中的字符串類型詳細(xì)描述了字符串在內(nèi)存中的結(jié)構(gòu)及其類型信息。

本文主要研究字符串的各種操作(語(yǔ)法糖),在內(nèi)存中實(shí)際的樣子。

環(huán)境

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

聲明

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

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

操作類型

比較

  • 相等性比較
  • 不等性比較

連接(相加)

與[]byte的轉(zhuǎn)換

與[]byte的拷貝

代碼清單

  1. package main 
  2.  
  3. import ( 
  4.   "fmt" 
  5.  
  6. func main() { 
  7.   var array [20]byte 
  8.   var s = "copy hello world" 
  9.   string2slice(s) 
  10.   copyString(array[:], s) 
  11.   slice2string(array[:]) 
  12.   compare() 
  13.   concat() 
  14.  
  15. //go:noinline 
  16. func copyString(slice []byte, s string) { 
  17.   copy(slice, s) 
  18.   PrintSlice(slice) 
  19.  
  20. //go:noinline 
  21. func string2slice(s string) { 
  22.   PrintSlice([]byte(s)) 
  23.  
  24. //go:noinline 
  25. func slice2string(slice []byte) { 
  26.   PrintString(string(slice)) 
  27.  
  28. //go:noinline 
  29. func compare() { 
  30.   var h = "hello" 
  31.   var w = "world!" 
  32.   PrintBool(h > w) 
  33.   PrintBool(h < w) 
  34.   PrintBool(h >= w) 
  35.   PrintBool(h <= w) 
  36.   PrintBool(h != w) // PrintBool(true
  37.   PrintBool(h == w) // PrintBool(false
  38.   PrintBool(testEqual(h, w)) 
  39.   PrintBool(testNotEqual(h, w)) 
  40.  
  41. //go:noinline 
  42. func testEqual(h, w string) bool { 
  43.   return h == w 
  44.  
  45. //go:noinline 
  46. func testNotEqual(h, w string) bool { 
  47.   return h != w 
  48.  
  49. //go:noinline 
  50. func concat() { 
  51.   hello := "hello " 
  52.   world := "world" 
  53.   jack := "Jack" 
  54.   rose := " Rose " 
  55.   lucy := "Lucy" 
  56.   lily := " Lily " 
  57.   ex := "!" 
  58.   PrintString(concat2(hello, world)) 
  59.   PrintString(concat3(hello, jack, ex)) 
  60.   PrintString(concat4(hello, jack, rose, ex)) 
  61.   PrintString(concat5(hello, jack, rose, lucy, lily)) 
  62.   PrintString(concat6(hello, jack, rose, lucy, lily, ex)) 
  63.  
  64. //go:noinline 
  65. func concat2(a, b string) string { 
  66.   return a + b 
  67.  
  68. //go:noinline 
  69. func concat3(a, b, c string) string { 
  70.   return a + b + c 
  71.  
  72. //go:noinline 
  73. func concat4(a, b, c, d string) string { 
  74.   return a + b + c + d 
  75.  
  76. //go:noinline 
  77. func concat5(a, b, c, d, e string) string { 
  78.   return a + b + c + d + e 
  79.  
  80. //go:noinline 
  81. func concat6(a, b, c, d, e, f string) string { 
  82.   return a + b + c + d + e + f 
  83.  
  84. //go:noinline 
  85. func PrintBool(v bool) { 
  86.   fmt.Println("v =", v) 
  87.  
  88. //go:noinline 
  89. func PrintString(v string) { 
  90.   fmt.Println("s =", v) 
  91.  
  92. //go:noinline 
  93. func PrintSlice(s []byte) { 
  94.   fmt.Println("slice =", s) 
  • 添加go:noinline注解避免內(nèi)聯(lián),方便指令分析
  • 定義PrintBool/PrintSlice/PrintString函數(shù)避免編譯器插入runtime.convT*函數(shù)調(diào)用

深入內(nèi)存

字符串轉(zhuǎn)[]byte

代碼清單中的string2slice函數(shù)代碼非常簡(jiǎn)單,用于觀察[]byte(s)具體實(shí)現(xiàn)邏輯,編譯之后指令如下:

圖片

可以清晰地看到,我們?cè)诖a中的[]byte(s),被Go編譯器替換為runtime.stringtoslicebyte函數(shù)調(diào)用。

runtime.stringtoslicebyte函數(shù)定義在runtime/string.go源碼文件中,Go編譯器傳遞給該函數(shù)的buf參數(shù)值為nil。

  1. func stringtoslicebyte(buf *tmpBuf, s string) []byte { 
  2.   var b []byte 
  3.   if buf != nil && len(s) <= len(buf) { 
  4.     *buf = tmpBuf{} 
  5.     b = buf[:len(s)] 
  6.   } else { 
  7.     b = rawbyteslice(len(s)) 
  8.   } 
  9.   copy(b, s) 
  10.   return b 

rawbyteslice函數(shù)的功能是申請(qǐng)一塊內(nèi)存用于存儲(chǔ)拷貝后的數(shù)據(jù)。

[]byte轉(zhuǎn)字符串

代碼清單中的slice2string函數(shù)代碼非常簡(jiǎn)單,用于觀察string(slice)具體實(shí)現(xiàn)邏輯,編譯之后指令如下:

可以清晰地看到,我們?cè)诖a中的string(slice),被Go編譯器替換為runtime.slicebytetostring函數(shù)調(diào)用。

runtime.slicebytetostring函數(shù)定義在runtime/string.go源碼文件中,Go編譯器傳遞給該函數(shù)的buf參數(shù)值為nil。

拷貝字符串到[]byte

代碼清單中的copyString函數(shù)代碼非常簡(jiǎn)單,用于觀察copy(slice, s)具體實(shí)現(xiàn)邏輯,編譯之后指令如下:

這個(gè)邏輯稍微復(fù)雜一點(diǎn)點(diǎn),將以上指令再次翻譯為Go偽代碼如下:

  1. func copyString(slice reflect.SliceHeader, s reflect.StringHeader) { 
  2.     n := slice.Len 
  3.     if slice.Len > s.Len { 
  4.         n = s.Len 
  5.     } 
  6.     if slice.Data != s.Data { 
  7.         runtime.memmove(slice.Data, s.Data, n) 
  8.     } 
  9.     PrintSlice(*(*[]byte)(unsafe.Pointer(&slice))) 

可以看到,Go編譯器在copy(slice, s)這個(gè)簡(jiǎn)單易用語(yǔ)法糖背后做了很多的工作。

經(jīng)過比較,以上偽代碼與runtime/slice.go源碼文件中的slicecopy函數(shù)非常相似,但又不完全一致。

不等性比較

代碼清單中的compare函數(shù)測(cè)試了兩個(gè)字符串的各種比較操作。

查看該函數(shù)的指令,發(fā)現(xiàn)Go編譯器將以下四種比較操作全部轉(zhuǎn)換為runtime.cmpstring函數(shù)調(diào)用:

  • >
  • <
  • >=
  • <=

runtime.cmpstring函數(shù)是一個(gè)編譯器函數(shù),不會(huì)被直接調(diào)用,聲明在cmd/compile/internal/gc/builtin/runtime.go源碼文件中,由匯編語(yǔ)言實(shí)現(xiàn)。

GOARCH=amd64的實(shí)現(xiàn)位于internal/bytealg/compare_amd64.s源碼文件中。

該函數(shù)返回值可能是:

然后使用cmp匯編指令將返回值與0進(jìn)行比較,再使用以下匯編指令保存最終的比較結(jié)果(true / false):

在本例中,有兩個(gè)特殊的比較,分別被編譯為單條指令:

  • h != w 被編譯為 movb $0x1,(%rsp)
  • h == w 被編譯為 movb $0x0,(%rsp)

這是因?yàn)樵诒纠芯幾g器知道"hello"與"world"兩個(gè)字符串不相等,所以直接在編譯的時(shí)候直接把比較結(jié)果編譯到機(jī)器指令中。

所以,在代碼定義了testEqual和testNotEqual函數(shù)用于比較字符串變量。

相等性比較

關(guān)于相等性比較,在 內(nèi)存中的字符串類型 中已經(jīng)做了非常詳細(xì)的分析和說(shuō)明。

在本文的代碼清單中,testEqual函數(shù)指令如下,與runtime.strequal函數(shù)一致,是因?yàn)榫幾g器將runtime.strequal函數(shù)內(nèi)聯(lián)(inline)到了testEqual函數(shù)中。

出乎意料的是,!=與==編譯后的幾乎一致,只是兩處指令對(duì)結(jié)果進(jìn)行了相反的操作:

字符串連接(相加)

在本文的代碼清單中,concat函數(shù)用于觀察字符串的連接(+)操作,測(cè)試結(jié)果表明:

  • 2個(gè)字符串相加,實(shí)際調(diào)用runtime.concatstring2函數(shù)
  • 3個(gè)字符串相加,實(shí)際調(diào)用runtime.concatstring3函數(shù)
  • 4個(gè)字符串相加,實(shí)際調(diào)用runtime.concatstring4函數(shù)
  • 5個(gè)字符串相加,實(shí)際調(diào)用runtime.concatstring5函數(shù)
  • 超過5個(gè)字符串相加,實(shí)際調(diào)用runtime.concatstrings函數(shù)

以上這些函數(shù)調(diào)用,都是Go編譯器的代碼生成和插入工作。

在插入runtime.concatstring*函數(shù)的過程中,編譯器傳遞給這些函數(shù)的buf參數(shù)的值為nil。

runtime.concatstring*函數(shù)的實(shí)現(xiàn)非常簡(jiǎn)單,這里不再進(jìn)一步贅述。

小結(jié)

從以上詳細(xì)的分析可以看到,我們?cè)陂_發(fā)過程中,所有對(duì)字符串進(jìn)行的簡(jiǎn)單操作,都會(huì)被Go編譯器編碼為復(fù)雜的指令和函數(shù)調(diào)用。

許多開發(fā)者喜歡使用Go進(jìn)行開發(fā),理由是Go語(yǔ)言非常簡(jiǎn)單、簡(jiǎn)潔。

是的,我們都喜歡這種甜甜的語(yǔ)法糖。

而且,發(fā)掘語(yǔ)法糖背后的秘密,也是很好玩的事。

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

責(zé)任編輯:姜華 來(lái)源: Golang In Memory
相關(guān)推薦

2021-09-07 09:23:07

C++字符串算法

2010-09-06 17:30:46

SQL函數(shù)

2015-06-09 14:43:36

javascript操作字符串

2024-04-01 08:41:39

字符串.NET

2010-03-16 10:58:35

Python字符串

2009-07-15 17:20:45

Jython字符串

2009-11-27 10:24:25

PHP字符串操作

2021-03-08 08:57:00

Go 字符串測(cè)試

2024-10-30 16:49:00

Python字符串

2010-03-11 19:34:57

Python字符串

2010-07-14 12:57:59

Perl字符串

2010-06-28 15:18:51

SQL Server

2009-08-24 13:04:44

操作步驟C#字符串

2022-07-18 08:18:11

字符JavaJDK

2021-10-14 15:34:48

C語(yǔ)言字符串函數(shù)

2023-12-11 07:33:05

Go語(yǔ)言字符技巧

2023-01-03 08:07:33

Go字符串指針

2022-12-08 12:05:03

Bash字符串

2011-07-11 16:00:22

字符串拼接

2011-06-08 15:45:41

字符串JAVA
點(diǎn)贊
收藏

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