<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深度細(xì)節(jié) | Go 的 panic 秘密都在這

          共 9865字,需瀏覽 20分鐘

           ·

          2022-01-10 21:25

          關(guān)于 panic 的時(shí)機(jī),在上篇?深度細(xì)節(jié) | Go 的 panic 的三種誕生方式?對(duì) panic 總結(jié)三種誕生方式:

          • 程序猿主動(dòng):調(diào)用?panic( )?函數(shù);
          • 編譯器的隱藏代碼:比如除零場(chǎng)景;
          • 內(nèi)核發(fā)送給進(jìn)程信號(hào):比如非法地址訪問 ;

          三種都?xì)w一到?panic( )?函數(shù)的調(diào)用,指出 Go 的 panic 只是一個(gè)特殊的函數(shù)調(diào)用,是語(yǔ)言層面的處理。初學(xué) Go 的時(shí)候,奇伢心里也常常有些疑問:

          • panic 究竟是啥?是一個(gè)結(jié)構(gòu)體?還是一個(gè)函數(shù)?
          • 為什么 panic 會(huì)讓 Go 進(jìn)程退出的 ?
          • 為什么 recover 一定要放在 defer 里面才生效?
          • 為什么 recover 已經(jīng)放在 defer 里面,但是進(jìn)程還是沒有恢復(fù)?
          • 為什么 panic 之后,還能再 panic ?有啥影響?

          今天便是深入到代碼原理,明確解答以上問題。Go 源碼版本聲明?Go 1.13.5


          _panic 數(shù)據(jù)結(jié)構(gòu)


          看看?_panic?的數(shù)據(jù)結(jié)構(gòu):

          //?runtime/runtime2.go
          //?關(guān)鍵結(jié)構(gòu)體
          type?_panic?struct?{
          ????argp??????unsafe.Pointer
          ????arg???????interface{}????//?panic?的參數(shù)
          ????link??????*_panic????????//?鏈接下一個(gè)?panic?結(jié)構(gòu)體
          ????recovered?bool???????????//?是否恢復(fù),到此為止?
          ????aborted???bool???????????//?the?panic?was?aborted
          }

          重點(diǎn)字段關(guān)注

          • link?字段:一個(gè)指向?_panic?結(jié)構(gòu)體的指針,表明?_panic?和?_defer?類似,_panic?可以是一個(gè)單向鏈表,就跟?_defer?鏈表一樣;
          • recovered?字段:重點(diǎn)來(lái)了,所謂的?_panic?是否恢復(fù)其實(shí)就是看這個(gè)字段是否為 true,recover( )?其實(shí)就是修改這個(gè)字段;

          再看一下 goroutine 的兩個(gè)重要字段:

          type?g?struct?{
          ????//?...
          ????_panic?????????*_panic?//?panic?鏈表,這是最里的一個(gè)
          ????_defer?????????*_defer?// defer 鏈表,這是最里的一個(gè);
          ????//?...
          }

          從這里我們看出:_defer?和?_panic?鏈表都是掛在 goroutine 之上的。什么時(shí)候會(huì)導(dǎo)致?_panic?鏈表上多個(gè)元素?

          panic( )?的流程下,又調(diào)用了?panic( )?函數(shù)。

          這里有個(gè)細(xì)節(jié)要注意了,怎么才能做到?panic( )?流程里面再次調(diào)用?panic( )??

          劃重點(diǎn):只能是在 defer 函數(shù)上,才有可能形成一個(gè)?_panic?鏈表。因?yàn)?panic( )?函數(shù)內(nèi)只會(huì)執(zhí)行?_defer?函數(shù) !


          recover 函數(shù)


          為了方便講解,我們由簡(jiǎn)單的開始分析,先看 recover 函數(shù)究竟做了什么?

          defer?func()?{
          ????recover()
          }()

          recover?對(duì)應(yīng)了?runtime/panic.go?中的?gorecover?函數(shù)實(shí)現(xiàn)。


          ?1???gorecover 函數(shù)

          func?gorecover(argp?uintptr)?interface{}?{
          ????//?只處理 gp._panic 鏈表最新的這個(gè)?_panic;
          ????gp?:=?getg()
          ????p?:=?gp._panic
          ????if?p?!=?nil?&&?!p.recovered?&&?argp?==?uintptr(p.argp)?{
          ????????p.recovered?=?true
          ????????return?p.arg
          ????}
          ????return?nil
          }

          這個(gè)函數(shù)可太簡(jiǎn)單了:

          1. 取出當(dāng)前 goroutine 結(jié)構(gòu)體;
          2. 取出當(dāng)前 goroutine 的?_panic?鏈表最新的一個(gè)?_panic,如果是非 nil 值,則進(jìn)行處理;
          3. 該?_panic?結(jié)構(gòu)體的?recovered?賦值 true,程序返回;

          這就是 recover 函數(shù)的全部?jī)?nèi)容,只給?_panic.recovered?賦值而已,不涉及代碼的神奇跳轉(zhuǎn)。而?_panic.recovered?的賦值是在?panic?函數(shù)邏輯中發(fā)揮作用。


          panic 函數(shù)


          panic 的實(shí)現(xiàn)在一個(gè)叫做?gopanic?的函數(shù),位于?runtime/panic.go?文件。panic 機(jī)制最重要最重要的就是?gopanic?函數(shù)了,所有的 panic 細(xì)節(jié)盡在此。為什么 panic 會(huì)顯得晦澀,主要有兩個(gè)點(diǎn):

          1. 嵌套 panic 的時(shí)候,gopanic?會(huì)有遞歸執(zhí)行的場(chǎng)景;
          2. 程序指令跳轉(zhuǎn)并不是常規(guī)的函數(shù)壓棧,彈棧,在 recovery 的時(shí)候,是直接修改指令寄存器的結(jié)構(gòu)體,從而直接越過(guò)了 gopanic 后面的邏輯,甚至是多層 gopanic 遞歸的邏輯;
          一切秘密都在下面這個(gè)函數(shù):
          //?runtime/panic.go
          func?gopanic(e?interface{})?{
          ????//?在棧上分配一個(gè)?_panic?結(jié)構(gòu)體
          ????var?p?_panic
          ????//?把當(dāng)前最新的?_panic?掛到鏈表最前面
          ????p.link?=?gp._panic
          ????gp._panic?=?(*_panic)(noescape(unsafe.Pointer(&p)))
          ????
          ????for?{
          ????????//?取出當(dāng)前最近的 defer 函數(shù);
          ????????d?:=?gp._defer
          ????????if?d?==?nil?{
          ????????????//?如果沒有 defer ,那就沒有 recover 的時(shí)機(jī),只能跳到循環(huán)外,退出進(jìn)程了;
          ????????????break
          ????????}

          ????????//?進(jìn)到這個(gè)邏輯,那說(shuō)明了之前是有 panic 了,現(xiàn)在又有 panic 發(fā)生,這里一定處于遞歸之中;
          ????????if?d.started?{
          ????????????if?d._panic?!=?nil?{
          ????????????????d._panic.aborted?=?true
          ????????????}
          ????????????//?把這個(gè) defer 從鏈表中摘掉;
          ????????????gp._defer?=?d.link
          ????????????freedefer(d)
          ????????????continue
          ????????}

          ????????//?標(biāo)記?_defer?為?started?=?true?(panic?遞歸的時(shí)候有用)
          ????????d.started?=?true
          ????????//?記錄當(dāng)前?_defer?對(duì)應(yīng)的?panic
          ????????d._panic?=?(*_panic)(noescape(unsafe.Pointer(&p)))

          ????????//?執(zhí)行?defer?函數(shù)
          ????????reflectcall(nil,?unsafe.Pointer(d.fn),?deferArgs(d),?uint32(d.siz),?uint32(d.siz))

          ????????// defer 執(zhí)行完成,把這個(gè) defer 從鏈表里摘掉;
          ????????gp._defer?=?d.link
          ????????
          ????????//?取出 pc,sp 寄存器的值;
          ????????pc?:=?d.pc
          ????????sp?:=?unsafe.Pointer(d.sp)
          ????????//?如果?_panic 被設(shè)置成恢復(fù),那么到此為止;
          ????????if?p.recovered?{
          ????????????//?摘掉當(dāng)前的?_panic
          ????????????gp._panic?=?p.link
          ????????????//?如果前面還有 panic,并且是標(biāo)記了 aborted 的,那么也摘掉;
          ????????????for?gp._panic?!=?nil?&&?gp._panic.aborted?{
          ????????????????gp._panic?=?gp._panic.link
          ????????????}
          ????????????// panic 的流程到此為止,恢復(fù)到業(yè)務(wù)函數(shù)堆棧上執(zhí)行代碼;
          ????????????gp.sigcode0?=?uintptr(sp)
          ????????????gp.sigcode1?=?pc
          ????????????//?注意:恢復(fù)的時(shí)候 panic 函數(shù)將從此處跳出,本 gopanic 調(diào)用結(jié)束,后面的代碼永遠(yuǎn)都不會(huì)執(zhí)行。
          ????????????mcall(recovery)
          ????????????throw("recovery?failed")?//?mcall?should?not?return
          ????????}
          ????}

          ????//?打印錯(cuò)誤信息和堆棧,并且退出進(jìn)程;
          ????preprintpanics(gp._panic)
          ????fatalpanic(gp._panic)?//?should?not?return
          ????*(*int)(nil)?=?0??????//?not?reached
          }

          上面邏輯可以拆分為循環(huán)內(nèi)循環(huán)外兩部分去理解:

          • 循環(huán)內(nèi):程序執(zhí)行 defer,是否恢復(fù)正常的指令執(zhí)行,一切都在循環(huán)內(nèi)決定;
          • 循環(huán)外:一旦走到循環(huán)外,說(shuō)明?_panic?沒人處理,認(rèn)命吧,程序即將退出;


          ?for 循環(huán)內(nèi)


          循環(huán)內(nèi)的事情拆解成:

          1. 遍歷 goroutine 的 defer 鏈表,獲取到一個(gè)?_defer?延遲函數(shù);
          2. 獲取到?_defer?延遲函數(shù),設(shè)置標(biāo)識(shí)?d.started,綁定當(dāng)前?d._panic(用以在遞歸的時(shí)候判斷);
          3. 執(zhí)行?_defer?延遲函數(shù)
          4. 摘掉執(zhí)行完的?_defer?函數(shù);
          5. 判斷?_panic.recovered?是否設(shè)置為 true,進(jìn)行相應(yīng)操作;
            1. 如果是 true 那么重置 pc,sp 寄存器(一般從 deferreturn 指令前開始執(zhí)行),goroutine 投遞到調(diào)度隊(duì)列,等待執(zhí)行;
          6. 重復(fù)以上步驟;

          ?1???思考問題有答案了!


          你會(huì)發(fā)現(xiàn),更改?recovered?這個(gè)字段的時(shí)機(jī)只有在第三個(gè)步驟的時(shí)候。在任何地方,你都改不到?_panic.recovered?的值。

          問題一:為什么 recover 一定要放在 defer 里面才生效?

          因?yàn)?,這是唯一的修改?_panic.recovered?字段的時(shí)機(jī) !

          舉幾個(gè)對(duì)比的栗子:

          func?main()?{
          ????panic("test")
          ????recover()
          }

          上面的例子調(diào)用了?recover( )?為什么還是 panic ?

          因?yàn)?strong>根本執(zhí)行不到?recover?函數(shù),執(zhí)行順序是:

          ????panic?
          ????????gopanic
          ????????????執(zhí)行?defer?鏈表?
          ????????????exit

          有童鞋較真,那我把?recover()?放?panic("test")?前面唄?

          func?main()?{
          ????recover()
          ????panic("test")
          }

          不行,因?yàn)閳?zhí)行?recover?的時(shí)候,還沒有?_panic?掛在 goroutine 上面呢,recover?了個(gè)寂寞。

          問題二:為什么?recover?已經(jīng)放在?defer?里面,但是進(jìn)程還是沒有恢復(fù)?

          回憶一下上面 for 循環(huán)的操作:

          ????//?步驟:遍歷?_defer 鏈表
          ????d?:=?gp._defer
          ????//?步驟:執(zhí)行 defer 函數(shù)
          ????reflectcall(nil,?unsafe.Pointer(d.fn),?deferArgs(d),?uint32(d.siz),?uint32(d.siz))
          ????//?步驟:執(zhí)行完成,把這個(gè) defer 從鏈表里摘掉;
          ????gp._defer?=?d.link

          劃重點(diǎn):在?gopanic?里,只遍歷執(zhí)行當(dāng)前 goroutine 上的?_defer?函數(shù)鏈條。所以,如果掛在其他 goroutine 的?defer?函數(shù)做了?recover?,那么沒有絲毫用途。

          舉一個(gè)栗子:

          func?main()?{?//?g1
          ????go?func()?{?//?g2
          ????????defer?func()?{
          ????????????recover()
          ????????}()
          ????}()
          ????panic("test")
          }

          因?yàn)椋?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">panic?和?recover??在兩個(gè)不同的 goroutine,_panic?是掛在 g1 上的,recover?是在 g2 的?_defer?鏈條里。

          gopanic?遍歷的是 g1 的?_defer?函數(shù)鏈表,跟 g2 八桿子打不著,g2 的?recover?自然拿不到 g1 的?_panic?結(jié)構(gòu),自然也不能設(shè)置?recovered?為 true ,所以程序還是崩了。

          問題三:為什么 panic 之后,還能再 panic ?有啥影響?

          這個(gè)其實(shí)很容易理解,有些童鞋可能想復(fù)雜了。gopanic?只是一個(gè)函數(shù)調(diào)用而已,那函數(shù)調(diào)用為啥不能嵌套遞歸?

          當(dāng)然可以。

          觸發(fā)的場(chǎng)景一般是:

          • gopanic?函數(shù)調(diào)用?_defer?延遲函數(shù);
          • defer?延遲函數(shù)里面又調(diào)用了?panic/gopanic?函數(shù);

          這不就有了嘛,就是個(gè)簡(jiǎn)單的函數(shù)嵌套而已,沒啥不可以的,并且在這種場(chǎng)景下,_panic?結(jié)構(gòu)體就會(huì)從?gp._panic?開始形成了一個(gè)鏈表。

          而?gopanic?函數(shù)指令執(zhí)行的特殊在于兩點(diǎn):

          1. _panic?被人設(shè)置成 recovered 之后,重置 pc,sp 寄存器,直接跨越 gopanic (還有嵌套的函數(shù)棧),跳轉(zhuǎn)到正常業(yè)務(wù)流程中;
          2. 循環(huán)之外,等到最后,沒人處理?_panic?數(shù)據(jù),那就 exit 退出進(jìn)程,終止后續(xù)所有指令的執(zhí)行;

          舉個(gè)嵌套的栗子:

          func?main()?{
          ????defer?func()?{?//?延遲函數(shù)
          ????????panic("panic?again")
          ????}()
          ????panic("first")
          }

          函數(shù)執(zhí)行:

          ????gopanic
          ????????defer?延遲函數(shù)?
          ????????????gopanic
          ????????????????無(wú)?defer?延遲函數(shù)(遞歸往上),終止條件達(dá)成
          ????????????????
          ?????//?打印堆棧,退出程序
          ????fatalpanic

          童鞋你理解了嗎?下面就來(lái)考考你哦??匆粋€(gè)栗子:

          func?main()?{
          ????println("===?begin?===")
          ????defer?func()?{?//?defer_0
          ????????println("===?come?in?defer_0?===")
          ????}()
          ????defer?func()?{?//?defer_1
          ????????recover()
          ????}()
          ????defer?func()?{?//?defer_2
          ????????panic("panic?2")
          ????}()
          ????panic("panic?1")
          ????println("===?end?===")
          }

          上面的函數(shù)會(huì)出打印堆棧退出進(jìn)程嗎?

          答案是:不會(huì)。?猜一下輸出是啥?終端輸出結(jié)果如下:

          ???panic?./test_panic
          ===?begin?===
          ===?come?in?defer_0?===

          你猜對(duì)了嗎?奇伢給你梳理了一下完整的路線:

          main
          ????gopanic?//?第一次
          ????????1.?取出?defer_2,設(shè)置?started
          ????????2.?執(zhí)行?defer_2?
          ????????????gopanic?//?第二次
          ????????????????1.?取出?defer_2,panic?設(shè)置成?aborted
          ????????????????2.?把?defer_2?從鏈表中摘掉
          ????????????????3.?執(zhí)行?defer_1
          ????????????????????-?執(zhí)行?recover
          ????????????????4.?摘掉?defer_1
          ????????????????5.?執(zhí)行?recovery?,重置?pc?寄存器,跳轉(zhuǎn)到?defer_1?注冊(cè)時(shí)候,攜帶的指令,一般是跳轉(zhuǎn)到?deferreturn?上面幾個(gè)指令

          ????//?跳出 gopanic 的遞歸嵌套,直接到執(zhí)行 deferreturn 的地方;
          ????defereturn
          ????????1.?執(zhí)行?defer?函數(shù)鏈,鏈條上還剩一個(gè) defer_0,取出 defer_0;
          ????????2.?執(zhí)行?defer_0?函數(shù)
          ????//?main?函數(shù)結(jié)束

          再來(lái)一個(gè)對(duì)比的例子:

          func?main()?{
          ????println("===?begin?===")
          ????defer?func()?{?//?defer_0
          ????????println("===?come?in?defer_0?===")
          ????}()
          ????defer?func()?{?//?defer_1
          ????????panic("panic?2")????
          ????}()
          ????defer?func()?{?//?defer_2
          ????????recover()
          ????}()
          ????panic("panic?1")
          ????println("===?end?===")
          }

          上面的函數(shù)會(huì)打印堆棧,并且退出嗎?

          答案是:會(huì)。輸出如下:

          ???panic?./test_panic
          ===?begin?===
          ===?come?in?defer_0?===
          panic:?panic?2

          goroutine?1?[running]:
          main.main.func2()
          ?/Users/code/gopher/src/panic/test_panic.go:9?+0x39
          main.main()
          ?/Users/code/gopher/src/panic/test_panic.go:11?+0xf7

          奇伢給你梳理的執(zhí)行路徑如下:

          main
          ????gopanic?//?第一次
          ????????1.?取出?defer_2,設(shè)置?started
          ????????2.?執(zhí)行?defer_2?
          ????????????-?執(zhí)行?recover,panic_1?字段被設(shè)置?recovered
          ????????3.?把?defer_2?從鏈表中摘掉
          ????????4.?執(zhí)行?recovery?,重置?pc?寄存器,跳轉(zhuǎn)到?defer_1?注冊(cè)時(shí)候,攜帶的指令,一般是跳轉(zhuǎn)到?deferreturn?上面幾個(gè)指令

          ????//?跳出 gopanic 的遞歸嵌套,執(zhí)行到 deferreturn 的地方;
          ????defereturn

          ????????1.?遍歷?defer?函數(shù)鏈,取出?defer_1???
          ????????2.?摘掉?defer_1
          ????????2.?執(zhí)行?defer_1
          ????????????gopanic?//?第二次
          ????????????????1.?defer?鏈表上有個(gè) defer_0,取出來(lái);
          ????????????????2.?執(zhí)行?defer_0?(defer_0?沒有做?recover,只打印了一行輸出)
          ????????????????3.?摘掉?defer_0,鏈表為空,跳出?for?循環(huán)
          ????????????????3.?執(zhí)行?fatalpanic
          ????????????????????-?exit(2)?退出進(jìn)程

          你猜對(duì)了嗎?


          ?2???recovery 函數(shù)


          最后,看一下關(guān)鍵的?recovery?函數(shù)。在?gopanic?函數(shù)中,在循環(huán)執(zhí)行 defer 函數(shù)的時(shí)候,如果發(fā)現(xiàn)?_panic.recovered?字段被設(shè)置成 true 的時(shí)候,調(diào)用?mcall(recovery)?來(lái)執(zhí)行所謂的恢復(fù)。

          看一眼?recovery?函數(shù)的實(shí)現(xiàn),這個(gè)函數(shù)極其簡(jiǎn)單,就是恢復(fù) pc,sp 寄存器,重新把 Goroutine 投遞到調(diào)度隊(duì)列中。

          //?runtime/panic.go
          func?recovery(gp?*g)?{
          ????//?取出棧寄存器和程序計(jì)數(shù)器的值
          ????sp?:=?gp.sigcode0
          ????pc?:=?gp.sigcode1
          ????//?重置 goroutine 的 pc,sp 寄存器;
          ????gp.sched.sp?=?sp
          ????gp.sched.pc?=?pc
          ????//?重新投入調(diào)度隊(duì)列
          ????gogo(&gp.sched)
          }

          重置了 pc,sp 寄存器代表什么意思?

          pc 寄存器指向指令所在的地址,換句話說(shuō),就是跳轉(zhuǎn)到其他地方執(zhí)行指令去了。而不是順序執(zhí)行 gopanic 后面的指令了,補(bǔ)回來(lái)了。

          _defer.pc?的指令行,這個(gè)指令是哪里?

          這個(gè)要回憶一下?defer?的章節(jié),defer?注冊(cè)延遲函數(shù)的時(shí)候?qū)?yīng)一個(gè)?_defer?結(jié)構(gòu)體,在 new 這個(gè)結(jié)構(gòu)體的時(shí)候,_defer.pc?字段賦值的就是 new 函數(shù)的下一行指令。這個(gè)在?Golang 最細(xì)節(jié)篇 — 解密 defer 原理,究竟背著程序猿做了多少事情??詳細(xì)說(shuō)過(guò)。

          舉個(gè)例子,如果是棧上分配的話,那么在?deferprocStack?,所以,mcall(recovery)?跳轉(zhuǎn)到這個(gè)位置,其實(shí)后續(xù)就走?deferreturn?的邏輯了,執(zhí)行后續(xù)的?_defer?函數(shù)鏈。

          本次 panic 就到此為止,相當(dāng)于就恢復(fù)了程序的正常運(yùn)行。

          當(dāng)然,如果后續(xù)在 defer 函數(shù)里面又出現(xiàn) panic ,那可能形成一個(gè)?_panic?的鏈條,但是每一個(gè)的處理還是一樣的。

          劃重點(diǎn):函數(shù)的 call,ret 是最常見的指令跳轉(zhuǎn)。最本源的就是 pc 寄存器,函數(shù)壓棧,出棧的時(shí)候,修改的也是 pc 寄存器,在 recovery 流程里,則來(lái)的更直接一點(diǎn),直接改 pc ,sp。


          ?for 循環(huán)外

          走到 for 循環(huán)外,那程序 100% 要退出了。因?yàn)?fatalpanic 里面打印一些堆棧信息之后,直接調(diào)用 exit 退出進(jìn)程的。到這已經(jīng)沒有任何機(jī)會(huì)了,只能乖乖退出進(jìn)程。

          退出的調(diào)用就在?fatalpanic?里:

          func?fatalpanic(msgs?*_panic)?{
          ????//?1.?打印協(xié)程堆棧

          ????//?2.?退出進(jìn)程
          ????systemstack(func()?{
          ????????exit(2)
          ????})

          ????*(*int)(nil)?=?0?//?not?reached
          }

          所以這個(gè)問題清楚了嘛:為什么 panic 會(huì)讓 Go 進(jìn)程退出的 ?

          還能為啥,因?yàn)檎{(diào)用了 exit(2) 嘛。


          總結(jié)


          1. panic()?會(huì)退出進(jìn)程,是因?yàn)檎{(diào)用了 exit 的系統(tǒng)調(diào)用;
          2. recover()?并不是說(shuō)只能在 defer 里面調(diào)用,而是只能在 defer 函數(shù)中才能生效,只有在 defer 函數(shù)里面,才有可能遇到?_panic?結(jié)構(gòu)
          3. recover()?所在的 defer 函數(shù)必須和 panic 都是掛在同一個(gè) goroutine 上,不能跨協(xié)程,因?yàn)?gopanic?只會(huì)執(zhí)行當(dāng)前 goroutine 的延遲函數(shù);
          4. panic 的恢復(fù),就是重置 pc 寄存器,直接跳轉(zhuǎn)程序執(zhí)行的指令,跳轉(zhuǎn)到原本 defer 函數(shù)執(zhí)行完該跳轉(zhuǎn)的位置(deferreturn?執(zhí)行),從?gopanic?函數(shù)中跳出,不再回來(lái),自然就不會(huì)再?fatalpanic?;
          5. panic 為啥能嵌套?這個(gè)問題就像是在問為什么函數(shù)調(diào)用可以嵌套一樣,因?yàn)檫@個(gè)本質(zhì)是一樣的。


          ? ?


          喜歡明哥文章的同學(xué)
          歡迎長(zhǎng)按下圖訂閱!

          ???

          瀏覽 72
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  xjgggyxgs.com高价收liang,请涟系@qdd2000 | 豆花视频网页 | 91成人社区无码 | 欧美亚洲无码视频 | 亚洲AV首页 |