- 本文地址: https://www.laruence.com/2010/08/03/1697.html
- 转载请注明出处
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));
注: 图中有一处不严谨, 即在确定是否最后一个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)
有个问题,当发生内存超标时,有没有办法最后再执行一段自定义函数来dump需要的变量或者静态变量数据来排查内存使用问题或内存泄漏
学习!
2018祝博主发发发
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
[…] 废话不多说,先上图,此图来自鸟哥博客 […]
[…] 转载至这里 php异常处理流程 […]
[…] 22 Jun 10 ReflectionFunction(Method)引用参数导致Invocation failed 03 Aug 10 深入理解PHP原理之异常机制 […]
[…] 上一段代ç ä¸ï¼Œæˆ‘们希望æ•èŽ·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没有起作用。 […]
[…] 上一段代ç ä¸ï¼Œæˆ‘们希望æ•èŽ·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没有起作用。 […]
[…] 上一段代码中,我们希望捕获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没有起作用。 […]
对于一个请求只执行一次php 函数,有没有必要定义异常捕获?自己定义异常对比内置的错误处理有什么优势啊?
[…] 正如文章开头提问中的: “它的性能如何?”, 异常机制确实要比返回状态码的方式昂贵一些, 对于C++来说, 在异常发生的时候, 还要发生堆栈解退(对于PHP来说, 没有这个逻辑, 具体的大家可以参看我之间写的一篇文章: 深入理解PHP原理之异常机制). […]
[…] 正如文章开头提问中的: “它的性能如何?”, 异常机制确实要比返回状态码的方式昂贵一些, 对于C++来说, 在异常发生的时候, 还要发生堆栈解退(对于PHP来说, 没有这个逻辑, 具体的大家可以参看我之间写的一篇文章: 深入理解PHP原理之异常机制). […]
@laruence “默认函数的执行是在执行器完成后, 文章中有叙及.” 是不是可以这样理解:
“恩, 是在执行完成退出执行LOOP以后才判断是否有默认异常处理函数, 如果有才调用:”文中说的loop,指的是function onError(),而这个时候抛出的错误,按照流程图来理解的话,应该走到判断有没有exception_handler这一步的。因为是执行完loop才去判断自定义的exception_handler,所以判断为false,抛出php fatal error的错误,然后退出程序….
博主,能不能改进下php的报错啊,像这个 Fatal error: Exception thrown without a stack frame in Unknown on line 0 ,能不能准确的定位呢?现在出现这个错只能猜啊,还猜不对
楼主你的E-R图是 那个工具画得啊 ,好漂亮o ·
另外可否详细介绍一下try cache的机制呢?我也google了很多文章看过,结果还是神魂颠倒.
总感觉try cache是个鸡肋,习惯了js的try cache然后用php的感觉总是达不到预期的效果.
那既然如此,当程序中遇到E_COMPILE_ERROR错误时我们该如何捕捉呢?
如果设置一下Error_reporting让他不报错那是哑巴吃黄连,可是如果直接把错误输出到屏幕这也太那个啥了….
所以可否做到不管出现任何错误的时候都写入自己定义的log文件内呢?同时屏幕上不要报错出来.
Excellent job. The work is very much of particular focus to our team.
请问如何防止局部内存泄露问题,例如异常之前emalloc(n)的空间,在异常情况下如何释放.
@小兴 这个不行, fatal是致命的, 影响到了PHP本身执行的错误, 这种错误无法捕获。
您好,请教一个问题,我发现PHP是无法捕捉到Fatal Error致命错误的,不知道是否有其他方法能捕捉到呢?多谢!顺祝年快乐:)
多谢提醒
转载,备注出处了
http://blog.windphp.com/php/php-exception.html
另外你的blog回复出问题了
[…] 4: 深入理解PHP原理之异常机制 […]
@xiaobao 哦? 你的PHP是什么版本?
getMessage();
}
set_error_handler(“onError”);
set_exception_handler(“onException”);
/* 我从不会以我的名字命名文件, 所以这个文件不存在 */
require(“laruence.php”);
?>
上面的代码会输出什么呢?
我试了下,结果抛出的异常也被捕捉到了!!
什么原因,能解释下吗?万分感谢。。
@xiaobao 默认函数的执行是在执行器完成后, 文章中有叙及.
上面的流程图对吗?
如果没有一个catch捕捉到异常,那么还会判断有没有exception_handler,如果没有,才最终终止执行,弹出uncautch…
菜鸟来向博主学习了^
好难懂 还是看不懂!
异常机制
确实很深奥!
鸟哥威武。我基本看后不留言。今天还是留言吧!哈哈!
流程图是用什么工具做的?google docs 中的绘图吗?
[…] 深入理解PHP原理之异常机制 […]
看不懂!~~~~~~
对于解析错误致命错误之类,只有shutdown function可以接收或者拦截
但是理想条件下是该函数是绝对的第一个shutdown function, 不然会变得无法预估发生什么
沙发后再仔细看