国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語(yǔ)言編程技術(shù)及教程分享平臺(tái)!
分類導(dǎo)航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務(wù)器之家 - 腳本之家 - Golang - 深入分析golang多值返回以及閉包的實(shí)現(xiàn)

深入分析golang多值返回以及閉包的實(shí)現(xiàn)

2020-05-01 13:03daisy Golang

相對(duì)于C/C++,golang有很多新穎的特性,例如goroutine,channel等等,這些特性其實(shí)從golang源碼是可以理解其實(shí)現(xiàn)的原理。今天這篇文章主要來(lái)分析下golang多值返回以及閉包的實(shí)現(xiàn),因?yàn)檫@兩個(gè)實(shí)現(xiàn)golang源碼中并不存在,我們必須從匯編的

一、前言

golang有很多新穎的特性,不知道大家的使用的時(shí)候,有沒(méi)想過(guò),這些特性是如何實(shí)現(xiàn)的?當(dāng)然你可能會(huì)說(shuō),不了解這些特性好像也不影響自己使用golang,你說(shuō)的也有道理,但是,多了解底層的實(shí)現(xiàn)原理,對(duì)于在使用golang時(shí)的眼界是完全不一樣的,就類似于看過(guò)http的實(shí)現(xiàn)之后,再來(lái)使用http框架,和未看過(guò)http框架時(shí)的眼界是不一樣的,當(dāng)然,你如果是一名it愛(ài)好者,求知欲自然會(huì)引導(dǎo)你去學(xué)習(xí)。

二、這篇文章主要就分析兩點(diǎn):

     1、golang多值返回的實(shí)現(xiàn);

     2、golang閉包的實(shí)現(xiàn);

三、golang多值返回的實(shí)現(xiàn)

我們?cè)趯W(xué)C/C++時(shí),很多人應(yīng)該有了解過(guò)C/C++函數(shù)調(diào)用過(guò)程,參數(shù)是通過(guò)寄存器di和si(假設(shè)就兩個(gè)參數(shù))傳遞給被調(diào)用的函數(shù),被調(diào)用函數(shù)的返回結(jié)果只能是通過(guò)eax寄存器返回給調(diào)用函數(shù),因此C/C++函數(shù)只能返回一個(gè)值,那么我們是不是可以想象,golang的多值返回是否可以通過(guò)多個(gè)寄存器來(lái)實(shí)現(xiàn)的,正如用多個(gè)寄存器來(lái)傳參一樣?

這也是一種辦法,但是golang并沒(méi)有采用;我的理解是引入多個(gè)寄存器來(lái)存儲(chǔ)返回值,會(huì)引起多個(gè)寄存器用途的重新約定,這無(wú)疑增加了復(fù)雜度;可以這么說(shuō),golang的ABI與C/C++非常不一樣;

在從匯編角度分析golang多值返回之前,需要先熟悉golang匯編代碼的一些約定, golang官網(wǎng) 有說(shuō)明,這里重點(diǎn)說(shuō)明四個(gè)symbols,需要注意的是這里的寄存器是偽寄存器:

       1.FP 棧底寄存器,指向一個(gè)函數(shù)棧的頂部;

       2.PC 程序計(jì)數(shù)器,指向下一條執(zhí)行指令;

       3.SB 指向靜態(tài)數(shù)據(jù)的基指針,全局符號(hào);

       4.SP 棧頂寄存器;

這里面最重要的就是FP和SP,F(xiàn)P寄存器主要用于取參數(shù)以及存返回值,golang函數(shù)調(diào)用的實(shí)現(xiàn)很大程度上都是依賴這兩個(gè)寄存器,這里先給出結(jié)果,

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-----------+---\
| 返回值2 | \
+-----------+  \
| 返回值1 |  \
+---------+-+ 
| 參數(shù)2 |  這些在調(diào)用函數(shù)中
+-----------+ 
| 參數(shù)1 |   /
+-----------+  /
| 返回地址 | /
+-----------+--\/-----fp值
| 局部變量 | \
| ... | 被調(diào)用數(shù)棧禎
|   | /
+-----------+--/+---sp值

這個(gè)就是golang的一個(gè)函數(shù)棧,也是說(shuō)函數(shù)傳參是通過(guò)fp+offset來(lái)實(shí)現(xiàn)的,而多個(gè)返回值也是通過(guò)fp+offset存儲(chǔ)在調(diào)用函數(shù)的棧幀中。

下面通過(guò)一個(gè)例子來(lái)分析

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
 
import "fmt"
 
func test(i, j int) (int, int) {
a:=i+ j
b:=i- j
 return a,b
}
 
func main() {
a,b:= test(2,1)
 fmt.Println(a, b)
}

這個(gè)例子很簡(jiǎn)單,主要是為了說(shuō)明golang多值返回的過(guò)程;我們通過(guò)下面命令編譯該程序

go tool compile -S test.go > test.s

然后,就可以打開(kāi)test.s,來(lái)看下這個(gè)小程序的匯編代碼。首先來(lái)看下test函數(shù)的匯編代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"".test t=1size=32value=0args=0x20locals=0x0
0x000000000(test.go:5) TEXT"".test(SB),$0-32//棧大小為32字節(jié)
0x000000000(test.go:5)NOP
0x000000000(test.go:5)NOP
0x000000000(test.go:5)MOVQ"".i+8(FP),CX//取第一個(gè)參數(shù)i
0x000500005(test.go:5)MOVQ"".j+16(FP),AX//取第二個(gè)參數(shù)j
0x000a00010(test.go:5) FUNCDATA$0, gclocals·a8eabfc4a4514ed6b3b0c61e9680e440(SB)
0x000a00010(test.go:5) FUNCDATA$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000a00010(test.go:6)MOVQCX,BX//將i放入bx
0x000d00013(test.go:6) ADDQAX,CX//i+j放入cx
0x001000016(test.go:7) SUBQAX,BX//i-j放入bx
 //將返回結(jié)果存入調(diào)用函數(shù)棧幀
0x001300019(test.go:8)MOVQCX,"".~r2+24(FP)
 //將返回結(jié)果存入調(diào)用函數(shù)棧幀
0x001800024(test.go:8)MOVQBX,"".~r3+32(FP)
0x001d00029(test.go:8)RET

由這個(gè)匯編代碼可以看出來(lái),在test函數(shù)內(nèi)部,是通過(guò)fp+8取第一個(gè)參數(shù),fp+16取第二個(gè)參數(shù);然后將返回的第一個(gè)值存入fp+24,返回的第二個(gè)值存入fp+32,和我上述所說(shuō)完全一致;golang函數(shù)調(diào)用過(guò)程,是通過(guò)fp+offset來(lái)實(shí)現(xiàn)傳參和返回值,而不像C/C++都是通過(guò)寄存器實(shí)現(xiàn)傳參和返回值;

但是,這里有個(gè)問(wèn)題,我的變量都是int類型,為啥分配的都是8字節(jié),這有待考證。

本來(lái)想通過(guò)查看main函數(shù)的棧幀來(lái)驗(yàn)證之前的結(jié)論,但是golang對(duì)小函數(shù)自動(dòng)轉(zhuǎn)為內(nèi)聯(lián)函數(shù),因此你們可以自己編譯出來(lái)看看,main函數(shù)內(nèi)部是沒(méi)有調(diào)用test函數(shù)的,而是將test函數(shù)的匯編代碼直接拷貝進(jìn)main函數(shù)執(zhí)行了。

四、golang閉包的實(shí)現(xiàn)

之前有去看了下C++11的lambda函數(shù)的實(shí)現(xiàn),其實(shí)實(shí)現(xiàn)原理就是仿函數(shù);編譯器在編譯lambda函數(shù)時(shí),會(huì)生成一個(gè)匿名的仿函數(shù)類,然后執(zhí)行這個(gè)lambda函數(shù)時(shí),會(huì)調(diào)用編譯生成的匿名仿函數(shù)類重載函數(shù)調(diào)用方法,這個(gè)方法也就是lambda函數(shù)中定義的方法;其實(shí)golang閉包的實(shí)現(xiàn)和這個(gè)類似,我們通過(guò)例子來(lái)說(shuō)明

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
packagemain
 
import"fmt"
 
functest(aint)func(iint)int{
returnfunc(iint)int{
 a = a + i
returna
 }
}
 
funcmain(){
 f := test(1)
 a := f(2)
 fmt.Println(a)
 b := f(3)
 fmt.Println(b)
}

這個(gè)例子程序很簡(jiǎn)單,test函數(shù)傳入一個(gè)整型參數(shù)a,返回一個(gè)函數(shù)類型;這個(gè)函數(shù)類型傳入一個(gè)整型參數(shù)以及返回一個(gè)整型值;main函數(shù)調(diào)用test函數(shù),返回一個(gè)閉包函數(shù)。

來(lái)看下test函數(shù)的匯編代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
"".test t=1size=160value=0args=0x10locals=0x20
0x000000000(test.go:5) TEXT"".test(SB),$32-16
0x000000000(test.go:5)MOVQ(TLS),CX
0x000900009(test.go:5) CMPQSP,16(CX)
0x000d00013(test.go:5) JLS142
0x000f00015(test.go:5) SUBQ$32,SP
0x001300019(test.go:5) FUNCDATA$0, gclocals·8edb5632446ada37b0a930d010725cc5(SB)
0x001300019(test.go:5) FUNCDATA$1, gclocals·008e235a1392cc90d1ed9ad2f7e76d87(SB)
0x001300019(test.go:5) LEAQ type.int(SB),BX
0x001a00026(test.go:5)MOVQBX, (SP)
0x001e00030(test.go:5) PCDATA$0,$0
 //生成一個(gè)int型對(duì)象,即a
0x001e00030(test.go:5)CALLruntime.newobject(SB)
 //8(sp)即生成的a的地址,放入AX
0x002300035(test.go:5)MOVQ8(SP),AX
 //將a的地址存入sp+24的位置
0x002800040(test.go:5)MOVQAX,"".&a+24(SP)
 //取出main函數(shù)傳入的第一個(gè)參數(shù),即a
0x002d00045(test.go:5)MOVQ"".a+40(FP),BP
 //將a放入(AX)指向的內(nèi)存,即上述新生成的int型對(duì)象
0x003200050(test.go:5)MOVQBP, (AX)
0x003500053(test.go:6) LEAQ type.struct { F uintptr; a *int }(SB), BX
0x003c00060(test.go:6)MOVQBX, (SP)
0x004000064(test.go:6) PCDATA$0,$1
0x004000064(test.go:6)CALLruntime.newobject(SB)
 //8(sp)這就是上述生成的struct對(duì)象地址
0x004500069(test.go:6)MOVQ8(SP),AX
0x004a00074(test.go:6)NOP
 //test內(nèi)部匿名函數(shù)地址存入BP
0x004a00074(test.go:6) LEAQ"".test.func1(SB),BP
 //將匿名函數(shù)地址放入(AX)指向的地址,即給上述
 //F uintptr賦值
0x005100081(test.go:6)MOVQBP, (AX)
0x005400084(test.go:6)MOVQAX,"".autotmp_0001+16(SP)
0x005900089(test.go:6)NOP
 //將上述生成的整型對(duì)象a的地址存入BP
0x005900089(test.go:6)MOVQ"".&a+24(SP),BP
0x005e00094(test.go:6) CMPB runtime.writeBarrier(SB),$0
0x006500101(test.go:6)JNE$0,117
 //將a地址存入AX指向內(nèi)存+8,
 //即為上述結(jié)構(gòu)體a *int賦值
0x006700103(test.go:6)MOVQBP,8(AX)
 //將上述結(jié)構(gòu)體的地址存入main函數(shù)棧幀中;
0x006b00107(test.go:9)MOVQAX,"".~r1+48(FP)
0x007000112(test.go:9) ADDQ$32,SP
0x007400116(test.go:9)RET

之前有看到一句話,很形象地描述了閉包

類是有行為的數(shù)據(jù),為閉包是有數(shù)據(jù)的行為;

也就是說(shuō)閉包是有上下文的,我們以測(cè)試?yán)訛槔ㄟ^(guò)test函數(shù)生成的閉包函數(shù),都有各自的a,這個(gè)a就是閉包的上下文數(shù)據(jù),而且這個(gè)a一直伴隨著他的閉包函數(shù),每調(diào)用一次,a都會(huì)發(fā)生變化;

我們分析了上述匯編代碼,來(lái)看下閉包實(shí)現(xiàn)原理;在這個(gè)測(cè)試?yán)又?,由?code>a是閉包的上下文數(shù)據(jù),因此a必須在堆上分配,如果在棧上分配,函數(shù)結(jié)束,a也被回收了;然后會(huì)定義出一個(gè)匿名結(jié)構(gòu)體:

?
1
2
3
4
type.struct{
 F uintptr//這個(gè)就是閉包調(diào)用的函數(shù)指針
 a *int//這就是閉包的上下文數(shù)據(jù)
}

接著生成一個(gè)該對(duì)象,并將之前在堆上分配的整型對(duì)象a的地址賦值給結(jié)構(gòu)體中的a指針,接下來(lái)將閉包調(diào)用的func函數(shù)地址賦值給結(jié)構(gòu)體中F指針;這樣,每生成一個(gè)閉包函數(shù),其實(shí)就是生成一個(gè)上述結(jié)構(gòu)體對(duì)象,每個(gè)閉包對(duì)象也就有自己的數(shù)據(jù)a和調(diào)用函數(shù)F;最后將這個(gè)結(jié)構(gòu)體的地址返回給main函數(shù);

來(lái)看下main函數(shù)獲取閉包的過(guò)程;

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"".main t=1size=528value=0args=0x0locals=0x88
0x000000000(test.go:12) TEXT"".main(SB),$136-0
0x000000000(test.go:12)MOVQ(TLS),CX
0x000900009(test.go:12) LEAQ -8(SP),AX
0x000e00014(test.go:12) CMPQAX,16(CX)
0x001200018(test.go:12) JLS506
0x001800024(test.go:12) SUBQ$136,SP
0x001f00031(test.go:12) FUNCDATA$0, gclocals·f5be5308b59e045b7c5b33ee8908cfb7(SB)
0x001f00031(test.go:12) FUNCDATA$1, gclocals·9d868b227cedd8dd4b1bec8682560fff(SB)
 //將參數(shù)1(f:=test(1))放入main函數(shù)棧頂
0x001f00031(test.go:13)MOVQ$1, (SP)
0x002700039(test.go:13) PCDATA$0,$0
 //調(diào)用main函數(shù)生成閉包對(duì)象
0x002700039(test.go:13)CALL"".test(SB)
 //將閉包對(duì)象的地址放入DX
0x002c00044(test.go:13)MOVQ8(SP),DX
 //將參數(shù)2(a:=f(2))放入棧頂
0x003100049(test.go:14)MOVQ$2, (SP)
0x003900057(test.go:14)MOVQDX,"".f+56(SP)
 //將閉包對(duì)象的函數(shù)指針賦值給BX
0x003e00062(test.go:14)MOVQ(DX),BX
0x004100065(test.go:14) PCDATA$0,$1
 //這里調(diào)用閉包函數(shù),并且將閉包對(duì)象的地址也傳進(jìn)
 //閉包函數(shù),為了修改a嘛
0x004100065(test.go:14)CALLDX,BX
0x004300067(test.go:14)MOVQ8(SP),BX

很明顯,main函數(shù)調(diào)用test函數(shù)獲取的是閉包對(duì)象的地址,通過(guò)這個(gè)閉包對(duì)象地址找到閉包函數(shù),然后執(zhí)行這個(gè)閉包函數(shù),并且把閉包對(duì)象的地址傳進(jìn)函數(shù),這點(diǎn)和C++傳this指針原理一樣,為了修改成員變量a;

最后看下test內(nèi)部的匿名函數(shù)(閉包函數(shù)實(shí)現(xiàn)):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"".test.func1t=1size=32value=0args=0x10 locals=0x0
0x000000000(test.go:6) TEXT"".test.func1(SB), $0-16
0x000000000(test.go:6) NOP
0x000000000(test.go:6) NOP
0x000000000(test.go:6) FUNCDATA $0, gclocals·23e8278e2b69a3a75fa59b23c49ed6ad(SB)
0x000000000(test.go:6) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
//DX是閉包對(duì)象的地址,+8即a的地址
0x000000000(test.go:6) MOVQ8(DX), AX
//AX為a的地址,(AX)即為a的值
0x000400004(test.go:7) MOVQ (AX), BP
//將參數(shù)i存入R8
0x000700007(test.go:7) MOVQ"".i+8(FP), R8
//a+i的值存入BP
0x000c00012(test.go:7) ADDQ R8, BP
//將a+i存入a的地址
0x000f00015(test.go:7) MOVQ BP, (AX)
//將a地址最新數(shù)據(jù)存入BP
0x001200018(test.go:8) MOVQ (AX), BP
//將a最新值作為返回值放入main函數(shù)棧中
0x001500021(test.go:8) MOVQ BP,"".~r1+16(FP)
0x001a00026(test.go:8) RET

閉包函數(shù)的調(diào)用過(guò)程:

      1、通過(guò)閉包對(duì)象地址獲取閉包上下文數(shù)據(jù)a的地址;

      2、接著通過(guò)a的地址獲取到a的值,并與參數(shù)i相加;

      3、將a+i作為最新值存入a的地址;

      4、將a最新值返回給main函數(shù);

五、總結(jié)

這篇文章簡(jiǎn)單地從匯編角度分析了golang多值返回和閉包的實(shí)現(xiàn);

      多值返回主要是通過(guò)fp寄存器+offset獲取參數(shù)以及存入返回值實(shí)現(xiàn);

      閉包主要是通過(guò)在編譯時(shí)生成包含閉包函數(shù)和閉包上下文數(shù)據(jù)的結(jié)構(gòu)體實(shí)現(xiàn);

以上就是這篇文章的全部?jī)?nèi)容,希望對(duì)大家學(xué)習(xí)或只用golang能有一定的幫助,如果有疑問(wèn)大家可以留言交流。

延伸 · 閱讀

精彩推薦
  • Golanggolang 通過(guò)ssh代理連接mysql的操作

    golang 通過(guò)ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過(guò)ssh代理連接mysql的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧...

    a165861639710342021-03-08
  • GolangGolang中Bit數(shù)組的實(shí)現(xiàn)方式

    Golang中Bit數(shù)組的實(shí)現(xiàn)方式

    這篇文章主要介紹了Golang中Bit數(shù)組的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧...

    天易獨(dú)尊11682021-06-09
  • Golanggolang如何使用struct的tag屬性的詳細(xì)介紹

    golang如何使用struct的tag屬性的詳細(xì)介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細(xì)介紹,從例子說(shuō)起,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看...

    Go語(yǔ)言中文網(wǎng)11352020-05-21
  • Golanggo日志系統(tǒng)logrus顯示文件和行號(hào)的操作

    go日志系統(tǒng)logrus顯示文件和行號(hào)的操作

    這篇文章主要介紹了go日志系統(tǒng)logrus顯示文件和行號(hào)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧...

    SmallQinYan12302021-02-02
  • GolangGolang通脈之?dāng)?shù)據(jù)類型詳情

    Golang通脈之?dāng)?shù)據(jù)類型詳情

    這篇文章主要介紹了Golang通脈之?dāng)?shù)據(jù)類型,在編程語(yǔ)言中標(biāo)識(shí)符就是定義的具有某種意義的詞,比如變量名、常量名、函數(shù)名等等,Go語(yǔ)言中標(biāo)識(shí)符允許由...

    4272021-11-24
  • Golanggolang的httpserver優(yōu)雅重啟方法詳解

    golang的httpserver優(yōu)雅重啟方法詳解

    這篇文章主要給大家介紹了關(guān)于golang的httpserver優(yōu)雅重啟的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,...

    helight2992020-05-14
  • Golanggolang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉(zhuǎn)義的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧 ...

    李浩的life12792020-05-27
  • Golanggo語(yǔ)言制作端口掃描器

    go語(yǔ)言制作端口掃描器

    本文給大家分享的是使用go語(yǔ)言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
主站蜘蛛池模板: 电影91久久久| 一区亚洲 | www午夜视频| 色综合88| 香蕉久久久久久 | 天天精品 | 久久精品一级毛片 | 久草精品在线 | 视频一区在线观看 | 欧美日韩视频在线观看免费 | 岛国av在线免费观看 | 日韩久草| 国产日韩欧美精品 | 鲁一鲁综合 | 午夜久久久久 | 精品视频国产 | av免费在线观看网站 | 日韩在线电影 | 黑人中文字幕一区二区三区 | 少妇黄色一级片 | 欧美国产精品一区 | 国产欧美久久一区二区三区 | 日本视频网 | 91高清在线| 国产欧美精品一区二区三区 | 伊人99综合 | 亚洲国产精品久久人人爱 | 久久免费福利视频 | 日本视频免费高清一本18 | 一级片大片 | 国产成人精品久久 | 国产中文字幕在线观看 | av网站免费观看 | 亚洲精品久久久 | 国产乱xxxxx97国语对白 | 91精品国产欧美一区二区 | 久久国产亚洲视频 | 亚洲电影在线观看 | 毛片com | 精品中文字幕在线 | www.久久|