本文已参与「新人创作礼」活动,一起开启掘金创作之路。
目录
PHP内核使用C语言编写,而C语言实际是没有异常处理的,那PHP的异常处理是怎么实现的呢?
一、try-catch定义
PHP源码中,zend_try、zend_catch、zend_end_try的定义如下:
#define zend_try \
{ \
JMP_BUF *__orig_bailout = EG(bailout); \
JMP_BUF __bailout; \
\
EG(bailout) = &__bailout; \
if (SETJMP(__bailout)==0) {
#define zend_catch \
} else { \
EG(bailout) = __orig_bailout;
#define zend_end_try() \
} \
EG(bailout) = __orig_bailout; \
}
抛出异常使用的函数是zend_baildout,定义如下:
ZEND_API ZEND_COLD ZEND_NORETURN void _zend_bailout(const char *filename, uint32_t lineno) /* {{{ */
{
if (!EG(bailout)) {
zend_output_debug_string(1, "%s(%d) : Bailed out without a bailout address!", filename, lineno);
exit(-1);
}
gc_protect(1);
CG(unclean_shutdown) = 1;
CG(active_class_entry) = NULL;
CG(in_compilation) = 0;
EG(current_execute_data) = NULL;
LONGJMP(*EG(bailout), FAILURE);
}
而SETJMP和LONGJMP也是宏,真正对应的是
# define SETJMP(a) setjmp(a)
# define LONGJMP(a,b) longjmp(a, b)
# define JMP_BUF jmp_buf
二、setjmp和longjmp实现
1、setjmp
int setjmp(jmp_buf env)
这个函数的作用是:创建本地的jmp_buf缓冲区并且初始化,用于将来跳转回此处。这个子程序保存程序的调用环境于env参数所指的缓冲区,env将被longjmp使用。如果是从setjmp直接调用返回,setjmp返回值为0。如果是从longjmp恢复的程序调用环境返回,setjmp返回非零值。
2、longjmp
void longjmp(jmp_buf env, int value)
恢复最近一次调用 setjmp() 时保存的环境,jmp_buf 参数的设置是由之前调用 setjmp() 生成的。
来看一个例子
#include <setjmp.h>
#include <stdio.h>
jmp_buf env;
void jmp() {
longjmp(env, 999);
printf("after jmp 1\n");
}
void foo() {
int ret = setjmp(env);
if (ret == 0) {
printf("return %d\n", ret);
jmp();
printf("after jmp %d\n", ret);
} else {
printf("return %d\n", ret);
}
return;
}
int main(int argc, char* argv[]) {
foo();
return 0;
}
最终输出为:
return 0
return 999
可以看到使用longjmp实现了不同函数间的跳转,返回到zend_try和zend_catch里,当我们使用zend_bailout时,里面调用了longjmp返回bailout的值为-1,所以会走else分支(如果定义了的话)也就是对应的zend_catch里的内容。