msgbartop
PHP语言, PHP扩展, Zend引擎相关的研究,技术,新闻分享 – 左手代码 右手诗
msgbarbottom

03 Aug 10 深入理解PHP原理之异常机制

PHP的异常机制的原理是什么?

在PHP每一个可独立执行的op array最后的ZEND_HANDLE_EXCEPTION是用来干什么呢?

让我们从一个问题说起, 上周的时候, blue5tar提了一个问题:”对于下面的代码, onError明明执行了, 但是onException却没有执行, 为什么?”.

<?php
function onError($errCode, $errMesg, $errFile, $errLine) {
    echo "Error Occurred\n";
    throw new Exception($errMesg);
}

function onException($e) {
    echo $e->getMessage();
}

set_error_handler("onError");

set_exception_handler("onException");

/* 我从不会以我的名字命名文件, 所以这个文件不存在 */
require("laruence.php");

运行结果:

Error Occurred
PHP Fatal error:  main(): Failed opening required 'laruence.php'

首先, 我们要知道, Require在包含一个找不到的问题的时候, 会前后抛出俩个错误,

1. WARNING : 在PHP试图打开这个文件的时候抛出.
2. E_COMPILE_ERROR : 从PHP打开文件的函数返回失败以后抛出.

而我们知道, set_error_handler是不能捕获E_COMPILE_ERROR错误的:

The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.

所以, 在onError中, 只能捕获到第一个WARNING错误, 而在onError中抛出的异常, 为什么没有被默认exception_handler捕获呢?

这就要说说PHP的异常机制了.

了解opcode(深入理解PHP原理之Opcodes的同学都知道, 在PHP5.3以前, 每一个可独立运行的op array(文件, 函数, 方法)的最后一条opcode都是ZEND_HANDLE_EXCEPTION, 而这个opcode是做什么用的呢?

原来在PHP中, 当有异常被throw的时候, 会跳到每一个op array的最后一行, 来执行这条ZEND_HANDLE_EXCEPTION, 伪码如下:

void on_throw_exception(zval *exception TSRMLS_DC) {
1. 判断是否已经有异常抛出
2. 记录exception
3. 记录下一条要执行的op line的序号
4. 下一条要执行的op line序号 = 当前op array的最后一条
}

恩, 就和改写ip寄存器一样, 改写下一条要执行的op line的序号, 就改变了程序的流向, 这样, 就会进入到了ZEND_HANDLE_EXCEPTION的处理逻辑中.

而在ZEND_HANDLE_EXCEPTION中, 会判断这个异常是否在try catch中,

如果是    则把下一条要执行的op line, 置为第一个catch的op line, 并继续执行.
如果不是  则销毁一些不需要的变量, 和opline,  然后直接结束执行过程.

有的同学要问了:”那set_exception_handler设置的异常默认处理函数(user_exception_handler)什么时候起作用呢?”

恩, 是在执行完成退出执行LOOP以后才判断是否有默认异常处理函数, 如果有才调用:

//执行
zend_execute(EG(active_op_array) TSRMLS_CC);
if (EG(exception)) {
	if (EG(user_exception_handler)) {
		调用用户定义的默认异常处理函数
	} else {
		未捕获的异常
	}
} else {
	没有异常
}
destroy_op_array(EG(active_op_array) TSRMLS_CC);
efree(EG(active_op_array));

PHP异常流程

PHP异常流程


注: 图中有一处不严谨, 即在确定是否最后一个catch块的时候, 会同时判断(is_a), 如果是才进入最后一个catch块执行.

而PHP在遇到Fatal Error的时候, 会直接zend_bailout, 而zend_bailout会导致程序流程直接跳过上面代码段, 也可以理解为直接exit了(longjmp), 这就导致了user_exception_handler没有机会发生作用.

了解到这些, 我想文章开头的问题的为什么? 也就很清晰了吧?

最后, 关于ZEND_HANDLE_EXCEPTION, 也许有同学会有疑问: 如果是这样, 那为什么每一个可独立执行的op array最后都有这个ZEND_HANDLE_EXCEPTION呢? 最简单的, 如果一个函数中不会throw, 那么这个opcode 是明显不需要的啊? 嘿嘿, 你很聪明, PHP 5.3开始, 已经按照你的想法调整了.. 只有在throw时刻, 才会动态的生成ZEND_HANDLE_EXCEPTION opline.

PHP5 changelog:

Changed exception handling. Now each op_array doesn’t contain ZEND_HANDLE_EXCEPTION opcode in the end. (Dmitry)


分享到:



Related Posts:

Tags: , , , ,

35 Responses to “深入理解PHP原理之异常机制”

  1. 83Hung |

    Hello blogger, i must say you have hi quality articles here.
    Your page can go viral. You need initial traffic boost only.
    How to get it? Search for; Mertiso’s tips go viral

  2. 深入理解PHP原理之异常机制及错误处理机制 | WE ARE PERSI |

    [...] 废话不多说,先上图,此图来自鸟哥博客 [...]

  3. php异常机制 | Code Is Poetry |

    [...] 转载至这里 php异常处理流程 [...]

  4. 风雪之隅-鸟哥文章汇总 | 互联网菜鸟 |

    [...] 22 Jun 10 ReflectionFunction(Method)引用参数导致Invocation failed 03 Aug 10 深入理解PHP原理之异常机制 [...]

  5. php错误处理 – 西米糍 |

    [...] 上一段代ç ä¸­ï&frac14;Œæˆ‘们希望æ•èŽ·phpå‘生的所有错误ï&frac14;Œç„¶åŽè®°å&frac12;•logå‘é€å‘Šè­¦é‚®ä»¶ã€‚ä&frac12;†çŽ°å®žæ€»ä¸å°&frac12;如人æ„ï&frac14;šä¸æ˜¯æ‰€æœ‰é”™è¯¯éƒ&frac12;å¯ä»¥è¢«set_error_handlerè®&frac34;ç&frac12;®çš„错误å¥æŸ„æ•èŽ·ã€‚å&frac14;•ç”¨å®˜æ–&sup1;手册原文ï&frac14;šâ€œä»¥ä¸‹çº§åˆ«çš„错误ä¸èƒ&frac12;由用户定ä&sup1;‰çš„å‡&frac12;æ•°æ¥å¤„ç†ï&frac14;š E_ERROR〠E_PARSEã€E_CORE_ERROR〠E_CORE_WARNING〠E_COMPILE_ERROR〠E_COMPILE_WARNINGï&frac14;Œå’Œåœ¨è°ƒç”¨ set_error_handler()å‡&frac12;数所在文件中产生的大多数 E_STRICTâ€ã€‚官æ–&sup1;æ&sup2;¡æœ‰ç»™å‡ºåŽŸå› ï&frac14;Œä&frac12;†ä¸éš&frac34;看出这些错误è¦ä&sup1;ˆæ˜¯è¿è¡Œæ—¶çš„致å‘&frac12;错误ï&frac14;Œè¦ä&sup1;ˆæ˜¯php核心或ç&frac14;–译时的错误ï&frac14;Œå› æ­¤ä&sup1;Ÿä¸éš&frac34;猜æƒ&sup3;ï&frac14;šå¯&sup1;于è¿è¡Œæ—¶çš„致å‘&frac12;错误ï&frac14;Œphp直接中断ï&frac14;Œå¯&frac14;致了错误处ç†å‡&frac12;æ•°æ&sup2;¡æœ‰æœºä&frac14;šæ‰§è¡Œï&frac14;ˆè¯¦è§Laruence的这篇文章ï&frac14;‰ï&frac14;›å¯&sup1;于php核心或ç&frac14;–译时候å‘生的错误ï&frac14;Œæˆ‘们的php脚本æ &sup1;本ä¸èƒ&frac12;执行ï&frac14;Œæ‰€ä»¥set_error_handleræ&sup2;¡æœ‰èµ·ä&frac12;œç”¨ã€‚ [...]

  6. php错误处理 | 马路天使 – 西米糍 |

    [...] 上一段代ç ä¸­ï&frac14;Œæˆ‘们希望æ•èŽ·phpå‘生的所有错误ï&frac14;Œç„¶åŽè®°å&frac12;•logå‘é€å‘Šè­¦é‚®ä»¶ã€‚ä&frac12;†çŽ°å®žæ€»ä¸å°&frac12;如人æ„ï&frac14;šä¸æ˜¯æ‰€æœ‰é”™è¯¯éƒ&frac12;å¯ä»¥è¢«set_error_handlerè®&frac34;ç&frac12;®çš„错误å¥æŸ„æ•èŽ·ã€‚å&frac14;•ç”¨å®˜æ–&sup1;手册原文ï&frac14;šâ€œä»¥ä¸‹çº§åˆ«çš„错误ä¸èƒ&frac12;由用户定ä&sup1;‰çš„å‡&frac12;æ•°æ¥å¤„ç†ï&frac14;š E_ERROR〠E_PARSEã€E_CORE_ERROR〠E_CORE_WARNING〠E_COMPILE_ERROR〠E_COMPILE_WARNINGï&frac14;Œå’Œåœ¨è°ƒç”¨ set_error_handler()å‡&frac12;数所在文件中产生的大多数 E_STRICTâ€ã€‚官æ–&sup1;æ&sup2;¡æœ‰ç»™å‡ºåŽŸå› ï&frac14;Œä&frac12;†ä¸éš&frac34;看出这些错误è¦ä&sup1;ˆæ˜¯è¿è¡Œæ—¶çš„致å‘&frac12;错误ï&frac14;Œè¦ä&sup1;ˆæ˜¯php核心或ç&frac14;–译时的错误ï&frac14;Œå› æ­¤ä&sup1;Ÿä¸éš&frac34;猜æƒ&sup3;ï&frac14;šå¯&sup1;于è¿è¡Œæ—¶çš„致å‘&frac12;错误ï&frac14;Œphp直接中断ï&frac14;Œå¯&frac14;致了错误处ç†å‡&frac12;æ•°æ&sup2;¡æœ‰æœºä&frac14;šæ‰§è¡Œï&frac14;ˆè¯¦è§Laruence的这篇文章ï&frac14;‰ï&frac14;›å¯&sup1;于php核心或ç&frac14;–译时候å‘生的错误ï&frac14;Œæˆ‘们的php脚本æ &sup1;本ä¸èƒ&frac12;执行ï&frac14;Œæ‰€ä»¥set_error_handleræ&sup2;¡æœ‰èµ·ä&frac12;œç”¨ã€‚ [...]

  7. php错误处理 | Avery |

    [...] 上一段代码中,我们希望捕获php发生的所有错误,然后记录log发送告警邮件。但现实总不尽如人意:不是所有错误都可以被set_error_handler设置的错误句柄捕获。引用官方手册原文:“以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在调用 set_error_handler()函数所在文件中产生的大多数 E_STRICT”。官方没有给出原因,但不难看出这些错误要么是运行时的致命错误,要么是php核心或编译时的错误,因此也不难猜想:对于运行时的致命错误,php直接中断,导致了错误处理函数没有机会执行(详见Laruence的这篇文章);对于php核心或编译时候发生的错误,我们的php脚本根本不能执行,所以set_error_handler没有起作用。 [...]

  8. 简果 |

    对于一个请求只执行一次php 函数,有没有必要定义异常捕获?自己定义异常对比内置的错误处理有什么优势啊?

  9. 我们什么时候应该使用异常? | Jackie li's Blog |

    [...] 正如文章开头提问中的: “它的性能如何?”, 异常机制确实要比返回状态码的方式昂贵一些, 对于C++来说, 在异常发生的时候, 还要发生堆栈解退(对于PHP来说, 没有这个逻辑, 具体的大家可以参看我之间写的一篇文章: 深入理解PHP原理之异常机制). [...]

  10. Google Reader分享 » 我们什么时候应该使用异常? |

    [...] 正如文章开头提问中的: “它的性能如何?”, 异常机制确实要比返回状态码的方式昂贵一些, 对于C++来说, 在异常发生的时候, 还要发生堆栈解退(对于PHP来说, 没有这个逻辑, 具体的大家可以参看我之间写的一篇文章: 深入理解PHP原理之异常机制). [...]

  11. zhkzyth |

    @laruence “默认函数的执行是在执行器完成后, 文章中有叙及.” 是不是可以这样理解:

    “恩, 是在执行完成退出执行LOOP以后才判断是否有默认异常处理函数, 如果有才调用:”文中说的loop,指的是function onError(),而这个时候抛出的错误,按照流程图来理解的话,应该走到判断有没有exception_handler这一步的。因为是执行完loop才去判断自定义的exception_handler,所以判断为false,抛出php fatal error的错误,然后退出程序….

  12. xxx |

    博主,能不能改进下php的报错啊,像这个 Fatal error: Exception thrown without a stack frame in Unknown on line 0 ,能不能准确的定位呢?现在出现这个错只能猜啊,还猜不对

  13. Lun |

    楼主你的E-R图是 那个工具画得啊 ,好漂亮o ·

  14. seven |

    另外可否详细介绍一下try cache的机制呢?我也google了很多文章看过,结果还是神魂颠倒.
    总感觉try cache是个鸡肋,习惯了js的try cache然后用php的感觉总是达不到预期的效果.

  15. seven |

    那既然如此,当程序中遇到E_COMPILE_ERROR错误时我们该如何捕捉呢?
    如果设置一下Error_reporting让他不报错那是哑巴吃黄连,可是如果直接把错误输出到屏幕这也太那个啥了….
    所以可否做到不管出现任何错误的时候都写入自己定义的log文件内呢?同时屏幕上不要报错出来.

  16. Elwood Susana |

    Excellent job. The work is very much of particular focus to our team.

  17. gogo |

    请问如何防止局部内存泄露问题,例如异常之前emalloc(n)的空间,在异常情况下如何释放.

  18. 雪候鸟 |

    @小兴 这个不行, fatal是致命的, 影响到了PHP本身执行的错误, 这种错误无法捕获。

  19. 小兴 |

    您好,请教一个问题,我发现PHP是无法捕捉到Fatal Error致命错误的,不知道是否有其他方法能捕捉到呢?多谢!顺祝年快乐:)

  20. wind |

    多谢提醒
    转载,备注出处了
    http://blog.windphp.com/php/php-exception.html
    另外你的blog回复出问题了

  21. 美味分享4 | chenkun |

    [...] 4: 深入理解PHP原理之异常机制 [...]

  22. laruence |

    @xiaobao 哦? 你的PHP是什么版本?

  23. xiaobao |

    getMessage();
    }

    set_error_handler(“onError”);

    set_exception_handler(“onException”);

    /* 我从不会以我的名字命名文件, 所以这个文件不存在 */
    require(“laruence.php”);

    ?>

    上面的代码会输出什么呢?

    我试了下,结果抛出的异常也被捕捉到了!!

    什么原因,能解释下吗?万分感谢。。

  24. laruence |

    @xiaobao 默认函数的执行是在执行器完成后, 文章中有叙及.

  25. xiaobao |

    上面的流程图对吗?
    如果没有一个catch捕捉到异常,那么还会判断有没有exception_handler,如果没有,才最终终止执行,弹出uncautch…

  26. ShopEDHardyOnline |

    菜鸟来向博主学习了^

  27. air max shoes |

    好难懂 还是看不懂!

  28. 淘宝问答 |

    异常机制

  29. 蓝色夏威夷 |

    确实很深奥!

  30. 烂页 |

    鸟哥威武。我基本看后不留言。今天还是留言吧!哈哈!

  31. simplechen |

    流程图是用什么工具做的?google docs 中的绘图吗?

  32. 網站製作學習誌 » [Web] 連結分享 |

    [...] 深入理解PHP原理之异常机制 [...]

  33. 技术世界 |

    看不懂!~~~~~~

  34. MoontoC |

    对于解析错误致命错误之类,只有shutdown function可以接收或者拦截

    但是理想条件下是该函数是绝对的第一个shutdown function, 不然会变得无法预估发生什么

  35. trylife |

    沙发后再仔细看

Leave a Reply

*