php源碼-try内列、catch過程-原理

在php中我們通過try catch 捕獲異常, 通過 throw來拋出異常, 優(yōu)秀文章推薦:異常處理

先來說說try catch編譯完之后是什么內(nèi)容背率,比如說下面的代碼

<?php
echo "1111111\n";
try {
    $data_1 = "222222";
    throw new Exception("ddddddddddd");
} catch (\Exception $e) {
    echo "22222222\n";
} finally {
    echo "xxxxxxxxx\n";
}
echo "222222\n";

編譯后的OPCODE 和對應(yīng)的handler如下话瞧, (不知道如何看opcode的參考這篇php源碼-如何查看opcode源碼

opcode: 40 ZEND_ECHO , index:1401 ZEND_ECHO_SPEC_CONST_HANDLER
opcode: 38 ZEND_ASSIGN , index:1366 ZEND_ASSIGN_SPEC_CV_CONST_RETVAL_UNUSED_HANDLER
opcode: 68 ZEND_NEW , index:1672 ZEND_NEW_SPEC_CONST_HANDLER
opcode: 116 ZEND_SEND_VAL_EX , index:2488 ZEND_SEND_VAL_EX_SPEC_CONST_QUICK_HANDLER
opcode: 60 ZEND_DO_FCALL , index:1634 ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER
opcode: 108 ZEND_THROW , index:2369 ZEND_THROW_SPEC_VAR_HANDLER
opcode: 42 ZEND_JMP , index:1407 ZEND_JMP_SPEC_HANDLER
opcode: 107 ZEND_CATCH , index:2346 ZEND_CATCH_SPEC_CONST_CV_HANDLER
opcode: 40 ZEND_ECHO , index:1401 ZEND_ECHO_SPEC_CONST_HANDLER
opcode: 162 ZEND_FAST_CALL , index:3141 ZEND_FAST_CALL_SPEC_HANDLER
opcode: 42 ZEND_JMP , index:1407 ZEND_JMP_SPEC_HANDLER
opcode: 40 ZEND_ECHO , index:1401 ZEND_ECHO_SPEC_CONST_HANDLER
opcode: 163 ZEND_FAST_RET , index:3142 ZEND_FAST_RET_SPEC_HANDLER
opcode: 40 ZEND_ECHO , index:1401 ZEND_ECHO_SPEC_CONST_HANDLER
opcode: 62 ZEND_RETURN , index:1641 ZEND_RETURN_SPEC_CONST_HANDLER

怎么理解這個編譯結(jié)果呢,首先我們知道每個函數(shù)或者php文件最終都會編譯成一個opcode_array, 在_zend_op_array 結(jié)構(gòu)體中有一個異常處理的數(shù)據(jù)結(jié)構(gòu) try_catch_array

struct _zend_op_array {
        //...
    zend_try_catch_element *try_catch_array;
};


typedef struct _zend_try_catch_element {
    uint32_t try_op;     //try開始的opcode位置
    uint32_t catch_op;   //第1個catch塊的opcode位置
    uint32_t finally_op; //finally開始的opcode位置
    uint32_t finally_end;//finally結(jié)束的opcode位置
} zend_try_catch_element;

這個 try_catch_array 其實就定義了各個代碼塊的opcode位置寝姿,用于c語言層面的程序跳轉(zhuǎn)交排,比如異常發(fā)生了就可以jmp到catch_op 或者 finally_op處執(zhí)行。如果異常沒在當(dāng)前的opcode_array中被catch住饵筑,就會一層一層往上拋埃篓,如果都沒有被catch住,那么在最外層的opcode_array中就會結(jié)束當(dāng)前request的執(zhí)行(注意:這個結(jié)束request并不代表進(jìn)程退出根资,比如fpm sapi中代表request結(jié)束架专,進(jìn)程不退出, 這個跟各個sapi的實現(xiàn)有關(guān))

------------------------------------------------------------

以下內(nèi)容來自:異常處理

異常處理的編譯

異常捕獲及處理的語法:

try{
    try statement;
}catch(exception_class_1 $e){
    catch statement 1;
}catch(exception_class_2 $e){
    catch statement 2;
}finally{
    finally statement;
}

try表示要捕獲try statement中可能拋出的異常玄帕;catch是捕獲到異常后的處理部脚,可以定義多個,當(dāng)try中拋出異常時會依次檢查各個catch的異常類是否與拋出的匹配裤纹,如果匹配則有命中的那個catch塊處理委刘;finally為最后執(zhí)行的代碼,不管是否有異常拋出都會執(zhí)行鹰椒。

語法規(guī)則:

statement:
    ...
    |   T_TRY '{' inner_statement_list '}' catch_list finally_statement
            { $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); }
    ...
;
catch_list:
        /* empty */
            { $$ = zend_ast_create_list(0, ZEND_AST_CATCH_LIST); }
    |   catch_list T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
            { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CATCH, $4, $5, $8)); }
;
finally_statement:
        /* empty */ { $$ = NULL; }
    |   T_FINALLY '{' inner_statement_list '}' { $$ = $3; }
;

從語法規(guī)則可以看出锡移,try-catch-finally最終編譯為一個ZEND_AST_TRY節(jié)點,包含三個子節(jié)點吹零,分別是:try statement罩抗、catch list、finally statement灿椅,try statement套蒂、finally statement就是普通的ZEND_AST_STMT_LIST節(jié)點钞支,catch list包含多個ZEND_AST_CATCH節(jié)點,每個節(jié)點有三個子節(jié)點:exception class操刀、exception object及catch statement烁挟,最終生成的AST:

image

具體的編譯過程如下:

  • (1) 向所屬zend_op_array注冊一個zend_try_catch_element結(jié)構(gòu),所有try都會注冊一個這樣的結(jié)構(gòu)骨坑,與循環(huán)結(jié)構(gòu)注冊的zend_brk_cont_element類似撼嗓,當(dāng)前zend_op_array所有定義的異常保存在zend_op_array->try_catch_array數(shù)組中,這個結(jié)構(gòu)用來記錄try欢唾、catch以及finally開始的位置且警,具體結(jié)構(gòu):
typedef struct _zend_try_catch_element {
    uint32_t try_op;     //try開始的opcode位置
    uint32_t catch_op;   //第1個catch塊的opcode位置
    uint32_t finally_op; //finally開始的opcode位置
    uint32_t finally_end;//finally結(jié)束的opcode位置
} zend_try_catch_element;

  • (2) 編譯try statement,編譯完以后如果定義了catch塊則編譯一條ZEND_JMP礁遣,此opcode的作用時當(dāng)無異常拋出時跳過所有catch跳到finally或整個異常之外的斑芜,因為catch塊是在try statement之后編譯的,所以具體的跳轉(zhuǎn)值目前還無法確定祟霍;

  • (3) 依次編譯各個catch塊杏头,如果沒有定義則跳過此步驟,每個catch編譯時首先編譯一條ZEND_CATCH沸呐,此opcode保存著此catch的exception class醇王、exception object以及下一個catch塊開始的位置,編譯第1個catch時將此opcode的位置記錄在zend_try_catch_element.catch_op上崭添,接著編譯catch statement寓娩,最后編譯一條ZEND_JMP(最后一個catch不需要),此opcode的作用與步驟(2)的相同滥朱;

  • (4) 將步驟(2)根暑、步驟(3)中ZEND_JMP跳轉(zhuǎn)值設(shè)置為finally第1條opcode或異常定義之外的代碼,如果沒有定義finally則結(jié)束編譯徙邻,否則編譯finally塊排嫌,首先編譯一條ZEND_FAST_CALLZEND_JMP,接著編譯finally statement缰犁,最后編譯一條ZEND_FAST_RET淳地。

編譯完以后的結(jié)構(gòu):

image

異常的拋出通過throw一個異常對象來實現(xiàn),這個對象必須繼承>自Exception類帅容,拋出異常的語法:

throw exception_object;

throw的編譯比較簡單颇象,最終只編譯為一條opcode:ZEND_THROW

4.6.2 異常的拋出與捕獲

上一小節(jié)我們介紹了exception結(jié)構(gòu)在編譯階段的處理并徘,接下來我們再介紹下運行時exception的處理過程遣钳,這個過程相對比較復(fù)雜,整體的講其處理流程整體如下:

  • (1) 檢查拋出的是否是object麦乞,否則將導(dǎo)致error錯誤蕴茴;
  • (2) 將EG(exception)設(shè)置為拋出的異常對象劝评,同時將當(dāng)前stack(即:zend_execute_data)接下來要執(zhí)行的opcode設(shè)置為ZEND_HANDLE_EXCEPTION
  • (3) 執(zhí)行ZEND_HANDLE_EXCEPTION倦淀,查找匹配的catch:
    • (3.1) 首先遍歷當(dāng)前zend_op_array下定義的所有異常捕獲蒋畜,即zend_op_array->try_catch_array數(shù)組,然后根據(jù)throw的位置撞叽、try開始的位置姻成、catch開始的位置、finally開始的位置判斷判斷異常是否在try范圍內(nèi)愿棋,如果同時命中了多個try(即嵌套try的情況)則選擇最后那個(也就是最里層的)科展,遍歷完以后如果命中了則進(jìn)入步驟(3.2)處理,如果沒有命中當(dāng)前stack下任何try則進(jìn)入步驟(4)糠雨;
    • (3.2) 到這一步表示拋出的異常在當(dāng)前zend_op_array下有try攔截(注意這里只是表示異常在try中拋出的辛润,但是拋出的異常并一定能被catch),然后根據(jù)當(dāng)前try塊的zend_try_catch_element結(jié)構(gòu)取出第一個catch的位置见秤,將opcode設(shè)置為zend_try_catch_element.catch_op,跳到第一個catch塊開始的位置執(zhí)行真椿,即:執(zhí)行ZEND_CATCH鹃答;
    • (3.3) 執(zhí)行ZEND_CATCH,檢查拋出的異常對象是否與當(dāng)前catch的類型匹配突硝,檢查的過程為判斷兩個類是否存在父子關(guān)系测摔,如果匹配則表示異常被成功捕獲,將EG(exception)清空解恰,如果沒有則跳到下一個catch的位置重復(fù)步驟(3.3)锋八,如果到最后一個catch仍然沒有命中則在這個catch的位置拋出一個異常(實際還是原來按個異常,只是將拋出的位置轉(zhuǎn)移了當(dāng)前catch的位置)护盈,然后回到步驟(3);
  • (4) 當(dāng)前zend_op_array沒能成功捕獲異常挟纱,需要繼續(xù)往上拋:回到調(diào)用位置,將接下來要執(zhí)行的opcode設(shè)置為ZEND_HANDLE_EXCEPTION腐宋,比如函數(shù)中拋出了一個異常沒有在函數(shù)中捕獲紊服,則跳到調(diào)用的位置繼續(xù)捕獲,回到步驟(3)胸竞;如果到最終主腳本也沒有被捕獲則將結(jié)束執(zhí)行并導(dǎo)致error錯誤欺嗤。
image

這個過程最復(fù)雜的地方在于異常匹配、傳遞的過程卫枝,主要為ZEND_HANDLE_EXCEPTION煎饼、ZEND_CATCH兩條opcode之間的調(diào)用,當(dāng)拋出一個異常時會終止后面opcode的執(zhí)行校赤,轉(zhuǎn)向執(zhí)行ZEND_HANDLE_EXCEPTION吆玖,根據(jù)異常拋出的位置定位到最近的一個try的catch位置筒溃,如果這個catch沒有匹配則跳到下一個catch塊,然后再次執(zhí)行ZEND_HANDLE_EXCEPTION衰伯,如果到最后一個catch仍沒有匹配則將異常拋出前位置EG(opline_before_exception)更新為最后一個catch的位置铡羡,再次執(zhí)行ZEND_HANDLE_EXCEPTION,由于異常拋出的位置已經(jīng)更新了所以不會再匹配上次檢查過的那個catch意鲸,這個過程實際就是不斷遞歸執(zhí)行ZEND_HANDLE_EXCEPTION烦周、ZEND_CATCH;如果當(dāng)前zend_op_array都無法捕獲則將異常拋向上一個調(diào)用棧繼續(xù)捕獲怎顾,下面根據(jù)一個例子具體說明下:

function my_func(){
    //...
    throw new Exception("This is a exception from my_func()");
}

try{
    my_func();
}catch(ErrorException $e){
    echo "ErrorException";
}catch(Exception $e){
    echo "Exception";
}

my_func()中拋出了一個異常读慎,首先在my_func()中拋出一個異常,然后在my_func()的zend_op_array中檢查是不是能夠捕獲槐雾,發(fā)現(xiàn)沒有夭委,則回到調(diào)用的位置,再次檢查募强,第1次匹配到catch(ErrorException $e)株灸,檢查后發(fā)現(xiàn)并不匹配,然后跳到下一個catch塊繼續(xù)匹配擎值,第2次匹配到catch(Exception $e)慌烧,檢查后發(fā)現(xiàn)命中,捕獲成功鸠儿。

image

上面的過程并沒有提到finally的執(zhí)行時機(jī)屹蚊,首先要明確finally在哪些情況下會執(zhí)行,命中catch的情況比較簡單进每,即在catch statement執(zhí)行完以后跳到finally執(zhí)行汹粤,另外一種情況是如果一個異常在try中但沒有命中任何catch那么其finally也是會被執(zhí)行的,這種情況的finally實際是在步驟(3)中執(zhí)行的田晚,最后一個catch檢查完以后會更新異常拋出位置:EG(opline_before_exception)嘱兼,然后會再次執(zhí)行ZEND_HANDLE_EXCEPTION,再次檢查時就會發(fā)現(xiàn)沒有命中任何catch但命中finally了(因為異常位置更新了)贤徒,這時候就會將異常對象保存在finally塊中遭京,然后執(zhí)行finally,執(zhí)行完再將異常對象還原繼續(xù)捕獲

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泞莉,一起剝皮案震驚了整個濱河市哪雕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鲫趁,老刑警劉巖斯嚎,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡堡僻,警方通過查閱死者的電腦和手機(jī)糠惫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钉疫,“玉大人硼讽,你說我怎么就攤上這事∩螅” “怎么了固阁?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長城菊。 經(jīng)常有香客問我备燃,道長,這世上最難降的妖魔是什么凌唬? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任并齐,我火速辦了婚禮,結(jié)果婚禮上客税,老公的妹妹穿的比我還像新娘况褪。我一直安慰自己,他們只是感情好更耻,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布窝剖。 她就那樣靜靜地躺著,像睡著了一般酥夭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脊奋,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天熬北,我揣著相機(jī)與錄音,去河邊找鬼诚隙。 笑死讶隐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的久又。 我是一名探鬼主播巫延,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼地消!你這毒婦竟也來了炉峰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤脉执,失蹤者是張志新(化名)和其女友劉穎疼阔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡婆廊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年迅细,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淘邻。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡茵典,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宾舅,到底是詐尸還是另有隱情统阿,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布贴浙,位于F島的核電站砂吞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崎溃。R本人自食惡果不足惜蜻直,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望袁串。 院中可真熱鬧概而,春花似錦、人聲如沸囱修。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽破镰。三九已至餐曼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲜漩,已是汗流浹背源譬。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留孕似,地道東北人踩娘。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像喉祭,于是被迫代替她去往敵國和親养渴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容