日期:2010-09-21 浏览次数:20494 次
一、 内存
在PHP中,填充一个字符串变量相当简单,这只需求一个语句""即可,并且该字符串能够被自在地修正、拷贝和挪动。而在C言语中,虽然你能够编写例如"char *str = "hello world ";"这样的一个简单的静态字符串;但是,却不能修正该字符串,由于它生存于程序空间内。为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(例如strdup())来复制其内容。
以下为援用的内容: { char *str; str = strdup("hello world"); if (!str) { fprintf(stderr, "Unable to allocate memory!"); } } |
由于后面我们将分析的各种缘由,传统型内存管理函数(例如malloc(),free(),strdup(),realloc(),calloc(),等等)几乎都不能直接为PHP源代码所使用。
二、 释放内存
在几乎所有的平台上,内存管理都是通过一种请求和释放模式实现的。首先,一个使用程序请求它下面的层(通常指"操作系统"):"我想使用一些内存空间"。如果存在可用的空间,操作系统就会把它提供应该程序并且打上一个标记以便不会再把这部分内存分配给其它程序。
当使用程序使用完这部分内存,它应该被前往到OS;这样以来,它就能够被继续分配给其它程序。如果该程序不前往这部分内存,那么OS无法知道能否这块内存不再使用并进而再分配给另一个进程。如果一个内存块没有释放,并且所有者使用程序丢失了它,那么,我们就说此使用程序"存在漏洞",由于这部分内存无法再为其它程序可用。
在一个典型的客户端使用程序中,较小的不太经常的内存走漏有时能够为OS所"容忍",由于在这个进程稍后结束时该走漏内存会被隐式前往到OS。这并没有什么,由于OS知道它把该内存分配给了哪个程序,并且它能够确信当该程序终止时不再需求该内存。
而对于长时间运转的服务器守护程序,包括象Apache这样的web服务器和扩展php模块来说,进程往往被设计为相当长时间不断运转。由于OS不能清理内存使用,所以,任何程序的走漏-无论是多么小-都将导致反复操作并最终耗尽所有的系统资源。
如今,我们不妨考虑用户空间内的stristr()函数;为了使用大小写不敏感的搜索来查找一个字符串,它实际上创建了两个串的各自的一个小型副本,然后执行一个更传统型的大小写敏感的搜索来查找绝对的偏移量。然而,在定位该字符串的偏移量之后,它不再使用这些小写版本的字符串。如果它不释放这些副本,那么,每一个使用stristr()的脚本在每次调用它时都将走漏一些内存。最后,web服务器进程将拥有所有的系统内存,但却不能够使用它。
你可以理直气壮地说,理想的处理方案就是编写良好、干净的、分歧的代码。这当然不错;但是,在一个象PHP解释器这样的环境中,这种观点仅对了一半。
三、 错误处理
为了实现"跳出"对用户空间脚本及其依赖的扩展函数的一个活动请求,需求使用一种方法来完全"跳出"一个活动请求。这是在Zend引擎内实现的:在一个请求的开始设置一个"跳出"地址,然后在任何die()或exit()调用或在遇到任何关键错误(E_ERROR)时执行一个longjmp()以跳转到该"跳出"地址。
虽然这个"跳出"进程能够简化程序执行的流程,但是,在绝大多数情况下,这会意味着将会跳过资源清除代码部分(例如free()调用)并最终导致出现内存漏洞。如今,让我们来考虑下面这个简化版本的处理函数调用的引擎代码:
以下为援用的内容: void call_function(const char *fname, int fname_len TSRMLS_DC){ zend_function *fe; char *lcase_fname; /* PHP函数名是大小写不敏感的, *为了简化在函数表中对它们的定位, *所有函数名都隐含地翻译为小写的 */ lcase_fname = estrndup(fname, fname_len); zend_str_tolower(lcase_fname, fname_len); if (zend_hash_find(EG(function_table),lcase_fname, fname_len + 1, (void **)&fe) == FAILURE) { zend_execute(fe->op_array TSRMLS_CC); } else { php_error_docref(NULL TSRMLS_CC, E_ERROR,"Call to undefined function: %s()", fname); } efree(lcase_fname); } |
当执行到php_error_docref()这一行时,内部错误处理器就会明白该错误级别是critical,并相应地调用longjmp()来中缀当前程序流程并离开c