網(wǎng)站建設(shè)的招聘要求張家口網(wǎng)站seo
程序中編寫的函數(shù)在編譯階段會被編譯成一段段的指令存放在可執(zhí)行文件中,在程序運行階段這些內(nèi)存會加載到虛擬地址空間的代碼段。
當函數(shù)A調(diào)用了函數(shù)B的時候,對應(yīng)的會生成一條call指令,程序在運行到call指令時就會跳轉(zhuǎn)到對應(yīng)的B函數(shù)的代碼段的方法入口。每個函數(shù)最后還有一條ret指令,用于在函數(shù)執(zhí)行結(jié)束時跳回到調(diào)用處。
函數(shù)的??臻g一般從棧基bp開始到棧頂sp結(jié)束。從bp到sp依次存儲了
1.調(diào)用者棧基地址 caller bp
2.局部變量
3.返回值
4.參數(shù)
函數(shù)的運行需要一些關(guān)鍵信息,包括局部變量、參數(shù)、返回值等等。這些信息存放在內(nèi)存棧中。??臻g的數(shù)據(jù)后進先出,比如上面A調(diào)用B,會先加載A需要的信息到棧內(nèi),再調(diào)用到B時加載B需要的信息到棧內(nèi),B執(zhí)行完后將B用到的信息彈出棧。
call指令會將下一條指令的地址入棧,及A棧幀后面接了一條返回地址信息,然后跳轉(zhuǎn)到被調(diào)用函數(shù)入口出執(zhí)行,所以??臻g內(nèi)會入棧B函數(shù)的棧幀
現(xiàn)在??臻g內(nèi)依次為 :A棧幀->返回地址->B棧幀
程序運行時每個函數(shù)的棧布局都遵守統(tǒng)一的約定,所以被調(diào)用者可以通過棧指針+偏移量定位到特定的參數(shù)和返回值。
return關(guān)鍵字并不是原子性的,先是將返回值賦值,然后執(zhí)行defer函數(shù),在返回返回值
例1:
func func1(a int) int {defer func() {a++}()a++return a
}
func main() {a := func1(0)fmt.Println(a) // 輸出1
}
- a++,參數(shù)a=1
- 執(zhí)行return,將1賦值給返回值空間,此時返回值為1
- 執(zhí)行defer,將參數(shù)a++,此時a=2,但返回值空間依然為1
- 函數(shù)調(diào)用結(jié)束,返回1
再看一段代碼
func func1(a int) (b int) {defer func() {a++b++}()a++return a
}
func main() {a := func1(0)fmt.Println(a) // 輸出2
}
- a++,參數(shù)a=1
- 執(zhí)行return,將1賦值給返回值空間,此時返回值為1
- 執(zhí)行defer,將參數(shù)a++,此時a=2,然后將返回值b++,此時b=2
- 調(diào)用結(jié)束,返回2
理解了函數(shù)調(diào)用時數(shù)據(jù)的分配就可以理解上面的問題。
另一個關(guān)鍵點是指針參數(shù)問題
func func1(a *int) {defer func() {*a++}()*a++return
}
func main() {a := 0func1(&a)fmt.Println(a) // 輸出2
}
golang中方法都是值傳遞,但是傳遞的值是指針類型,里面存放的是數(shù)據(jù)的地址。
下面看一下引用類型的例子
func func1(a []int) {a[0] = 1return
}
func main() {a := []int{0}func1(a)fmt.Println(a) // 輸出[1]
}
這段代碼中 調(diào)用func1時傳遞的是slice類型的參數(shù),slice類型是引用的底層數(shù)據(jù),所以func1改變數(shù)據(jù)底層數(shù)據(jù)時,main中的局部變量a也受到了改變。
func func1(a []int) {a = append(a, 1, 2, 3)return
}
func main() {a := []int{0}func1(a)fmt.Println(a) // 輸出[0]
}
這段代碼因為func1對a執(zhí)行了append,觸發(fā)了slice的擴容,底層開辟了一個新的數(shù)組并重新引用了新的數(shù)組,所以原數(shù)組沒有受到影響。