亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入探討PHP中的內(nèi)存管理問(wèn)題

 更新時(shí)間:2011年08月31日 23:39:39   作者:  
內(nèi)存管理對(duì)于長(zhǎng)期運(yùn)行的程序,例如服務(wù)器守護(hù)程序,是相當(dāng)重要的影響;因此,理解PHP是如何分配與釋放內(nèi)存的對(duì)于創(chuàng)建這類(lèi)程序極為重要。本文將重點(diǎn)探討PHP的內(nèi)存管理問(wèn)題。
一、 內(nèi)存

  在PHP中,填充一個(gè)字符串變量相當(dāng)簡(jiǎn)單,這只需要一個(gè)語(yǔ)句"<?php $str = 'hello world '; ?>"即可,并且該字符串能夠被自由地修改、拷貝和移動(dòng)。而在C語(yǔ)言中,盡管你能夠編寫(xiě)例如"char *str = "hello world ";"這樣的一個(gè)簡(jiǎn)單的靜態(tài)字符串;但是,卻不能修改該字符串,因?yàn)樗嬗诔绦蚩臻g內(nèi)。為了創(chuàng)建一個(gè)可操縱的字符串,你必須分配一個(gè)內(nèi)存塊,并且通過(guò)一個(gè)函數(shù)(例如strdup())來(lái)復(fù)制其內(nèi)容。

{
 char *str;
 str = strdup("hello world");
 if (!str) {
  fprintf(stderr, "Unable to allocate memory!");
 }
}

  由于后面我們將分析的各種原因,傳統(tǒng)型內(nèi)存管理函數(shù)(例如malloc(),free(),strdup(),realloc(),calloc(),等等)幾乎都不能直接為PHP源代碼所使用。

  二、 釋放內(nèi)存

  在幾乎所有的平臺(tái)上,內(nèi)存管理都是通過(guò)一種請(qǐng)求和釋放模式實(shí)現(xiàn)的。首先,一個(gè)應(yīng)用程序請(qǐng)求它下面的層(通常指"操作系統(tǒng)"):"我想使用一些內(nèi)存空間"。如果存在可用的空間,操作系統(tǒng)就會(huì)把它提供給該程序并且打上一個(gè)標(biāo)記以便不會(huì)再把這部分內(nèi)存分配給其它程序。
當(dāng)應(yīng)用程序使用完這部分內(nèi)存,它應(yīng)該被返回到OS;這樣以來(lái),它就能夠被繼續(xù)分配給其它程序。如果該程序不返回這部分內(nèi)存,那么OS無(wú)法知道是否這塊內(nèi)存不再使用并進(jìn)而再分配給另一個(gè)進(jìn)程。如果一個(gè)內(nèi)存塊沒(méi)有釋放,并且所有者應(yīng)用程序丟失了它,那么,我們就說(shuō)此應(yīng)用程序"存在漏洞",因?yàn)檫@部分內(nèi)存無(wú)法再為其它程序可用。

  在一個(gè)典型的客戶(hù)端應(yīng)用程序中,較小的不太經(jīng)常的內(nèi)存泄漏有時(shí)能夠?yàn)镺S所"容忍",因?yàn)樵谶@個(gè)進(jìn)程稍后結(jié)束時(shí)該泄漏內(nèi)存會(huì)被隱式返回到OS。這并沒(méi)有什么,因?yàn)镺S知道它把該內(nèi)存分配給了哪個(gè)程序,并且它能夠確信當(dāng)該程序終止時(shí)不再需要該內(nèi)存。

  而對(duì)于長(zhǎng)時(shí)間運(yùn)行的服務(wù)器守護(hù)程序,包括象Apache這樣的web服務(wù)器和擴(kuò)展php模塊來(lái)說(shuō),進(jìn)程往往被設(shè)計(jì)為相當(dāng)長(zhǎng)時(shí)間一直運(yùn)行。因?yàn)镺S不能清理內(nèi)存使用,所以,任何程序的泄漏-無(wú)論是多么小-都將導(dǎo)致重復(fù)操作并最終耗盡所有的系統(tǒng)資源。

  現(xiàn)在,我們不妨考慮用戶(hù)空間內(nèi)的stristr()函數(shù);為了使用大小寫(xiě)不敏感的搜索來(lái)查找一個(gè)字符串,它實(shí)際上創(chuàng)建了兩個(gè)串的各自的一個(gè)小型副本,然后執(zhí)行一個(gè)更傳統(tǒng)型的大小寫(xiě)敏感的搜索來(lái)查找相對(duì)的偏移量。然而,在定位該字符串的偏移量之后,它不再使用這些小寫(xiě)版本的字符串。如果它不釋放這些副本,那么,每一個(gè)使用stristr()的腳本在每次調(diào)用它時(shí)都將泄漏一些內(nèi)存。最后,web服務(wù)器進(jìn)程將擁有所有的系統(tǒng)內(nèi)存,但卻不能夠使用它。

  你可以理直氣壯地說(shuō),理想的解決方案就是編寫(xiě)良好、干凈的、一致的代碼。這當(dāng)然不錯(cuò);但是,在一個(gè)象PHP解釋器這樣的環(huán)境中,這種觀點(diǎn)僅對(duì)了一半。

  三、 錯(cuò)誤處理

  為了實(shí)現(xiàn)"跳出"對(duì)用戶(hù)空間腳本及其依賴(lài)的擴(kuò)展函數(shù)的一個(gè)活動(dòng)請(qǐng)求,需要使用一種方法來(lái)完全"跳出"一個(gè)活動(dòng)請(qǐng)求。這是在Zend引擎內(nèi)實(shí)現(xiàn)的:在一個(gè)請(qǐng)求的開(kāi)始設(shè)置一個(gè)"跳出"地址,然后在任何die()或exit()調(diào)用或在遇到任何關(guān)鍵錯(cuò)誤(E_ERROR)時(shí)執(zhí)行一個(gè)longjmp()以跳轉(zhuǎn)到該"跳出"地址。

  盡管這個(gè)"跳出"進(jìn)程能夠簡(jiǎn)化程序執(zhí)行的流程,但是,在絕大多數(shù)情況下,這會(huì)意味著將會(huì)跳過(guò)資源清除代碼部分(例如free()調(diào)用)并最終導(dǎo)致出現(xiàn)內(nèi)存漏洞。現(xiàn)在,讓我們來(lái)考慮下面這個(gè)簡(jiǎn)化版本的處理函數(shù)調(diào)用的引擎代碼:

void call_function(const char *fname, int fname_len TSRMLS_DC){
 zend_function *fe;
 char *lcase_fname;
 /* PHP函數(shù)名是大小寫(xiě)不敏感的,
 *為了簡(jiǎn)化在函數(shù)表中對(duì)它們的定位,
 *所有函數(shù)名都隱含地翻譯為小寫(xiě)的
 */
 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);
}


  當(dāng)執(zhí)行到php_error_docref()這一行時(shí),內(nèi)部錯(cuò)誤處理器就會(huì)明白該錯(cuò)誤級(jí)別是critical,并相應(yīng)地調(diào)用longjmp()來(lái)中斷當(dāng)前程序流程并離開(kāi)call_function()函數(shù),甚至根本不會(huì)執(zhí)行到efree(lcase_fname)這一行。你可能想把efree()代碼行移動(dòng)到zend_error()代碼行的上面;但是,調(diào)用這個(gè)call_function()例程的代碼行會(huì)怎么樣呢?fname本身很可能就是一個(gè)分配的字符串,并且,在它被錯(cuò)誤消息處理使用完之前,你根本不能釋放它。

  注意,這個(gè)php_error_docref()函數(shù)是trigger_error()函數(shù)的一個(gè)內(nèi)部等價(jià)實(shí)現(xiàn)。它的第一個(gè)參數(shù)是一個(gè)將被添加到docref的可選的文檔引用。第三個(gè)參數(shù)可以是任何我們熟悉的E_*家族常量,用于指示錯(cuò)誤的嚴(yán)重程度。第四個(gè)參數(shù)(最后一個(gè))遵循printf()風(fēng)格的格式化和變量參數(shù)列表式樣。

  四、 Zend內(nèi)存管理器

  在上面的"跳出"請(qǐng)求期間解決內(nèi)存泄漏的方案之一是:使用Zend內(nèi)存管理(ZendMM)層。引擎的這一部分非常類(lèi)似于操作系統(tǒng)的內(nèi)存管理行為-分配內(nèi)存給調(diào)用程序。區(qū)別在于,它處于進(jìn)程空間中非常低的位置而且是"請(qǐng)求感知"的;這樣以來(lái),當(dāng)一個(gè)請(qǐng)求結(jié)束時(shí),它能夠執(zhí)行與OS在一個(gè)進(jìn)程終止時(shí)相同的行為。也就是說(shuō),它會(huì)隱式地釋放所有的為該請(qǐng)求所占用的內(nèi)存。圖1展示了ZendMM與OS以及PHP進(jìn)程之間的關(guān)系。

深入探討PHP中的內(nèi)存管理問(wèn)題
圖1.Zend內(nèi)存管理器代替系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)針對(duì)每一種請(qǐng)求的內(nèi)存分配。


  除了提供隱式內(nèi)存清除功能之外,ZendMM還能夠根據(jù)php.ini中memory_limit的設(shè)置控制每一種內(nèi)存請(qǐng)求的用法。如果一個(gè)腳本試圖請(qǐng)求比系統(tǒng)中可用內(nèi)存更多的內(nèi)存,或大于它每次應(yīng)該請(qǐng)求的最大量,那么,ZendMM將自動(dòng)地發(fā)出一個(gè)E_ERROR消息并且啟動(dòng)相應(yīng)的"跳出"進(jìn)程。這種方法的一個(gè)額外優(yōu)點(diǎn)在于,大多數(shù)內(nèi)存分配調(diào)用的返回值并不需要檢查,因?yàn)槿绻〉脑拰?huì)導(dǎo)致立即跳轉(zhuǎn)到引擎的退出部分。

  把PHP內(nèi)部代碼和OS的實(shí)際的內(nèi)存管理層"鉤"在一起的原理并不復(fù)雜:所有內(nèi)部分配的內(nèi)存都要使用一組特定的可選函數(shù)實(shí)現(xiàn)。例如,PHP代碼不是使用malloc(16)來(lái)分配一個(gè)16字節(jié)內(nèi)存塊而是使用了emalloc(16)。除了實(shí)現(xiàn)實(shí)際的內(nèi)存分配任務(wù)外,ZendMM還會(huì)使用相應(yīng)的綁定請(qǐng)求類(lèi)型來(lái)標(biāo)志該內(nèi)存塊;這樣以來(lái),當(dāng)一個(gè)請(qǐng)求"跳出"時(shí),ZendMM可以隱式地釋放它。

  經(jīng)常情況下,內(nèi)存一般都需要被分配比單個(gè)請(qǐng)求持續(xù)時(shí)間更長(zhǎng)的一段時(shí)間。這種類(lèi)型的分配(因其在一次請(qǐng)求結(jié)束之后仍然存在而被稱(chēng)為"永久性分配"),可以使用傳統(tǒng)型內(nèi)存分配器來(lái)實(shí)現(xiàn),因?yàn)檫@些分配并不會(huì)添加ZendMM使用的那些額外的相應(yīng)于每種請(qǐng)求的信息。然而有時(shí),直到運(yùn)行時(shí)刻才會(huì)確定是否一個(gè)特定的分配需要永久性分配,因此ZendMM導(dǎo)出了一組幫助宏,其行為類(lèi)似于其它的內(nèi)存分配函數(shù),但是使用最后一個(gè)額外參數(shù)來(lái)指示是否為永久性分配。

  如果你確實(shí)想實(shí)現(xiàn)一個(gè)永久性分配,那么這個(gè)參數(shù)應(yīng)該被設(shè)置為1;在這種情況下,請(qǐng)求是通過(guò)傳統(tǒng)型malloc()分配器家族進(jìn)行傳遞的。然而,如果運(yùn)行時(shí)刻邏輯認(rèn)為這個(gè)塊不需要永久性分配;那么,這個(gè)參數(shù)可以被設(shè)置為零,并且調(diào)用將會(huì)被調(diào)整到針對(duì)每種請(qǐng)求的內(nèi)存分配器函數(shù)。

  例如,pemalloc(buffer_len,1)將映射到malloc(buffer_len),而pemalloc(buffer_len,0)將被使用下列語(yǔ)句映射到emalloc(buffer_len):

#define in Zend/zend_alloc.h:
#define pemalloc(size, persistent) ((persistent)?malloc(size): emalloc(size))

  所有這些在ZendMM中提供的分配器函數(shù)都能夠從下表中找到其更傳統(tǒng)的對(duì)應(yīng)實(shí)現(xiàn)。

  表格1展示了ZendMM支持下的每一個(gè)分配器函數(shù)以及它們的e/pe對(duì)應(yīng)實(shí)現(xiàn):

  表格1.傳統(tǒng)型相對(duì)于PHP特定的分配器。

分配器函數(shù) e/pe對(duì)應(yīng)實(shí)現(xiàn)
void *malloc(size_t count); void *emalloc(size_t count);void *pemalloc(size_t count,char persistent);
void *calloc(size_t count); void *ecalloc(size_t count);void *pecalloc(size_t count,char persistent);
void *realloc(void *ptr,size_t count); void *erealloc(void *ptr,size_t count);
void *perealloc(void *ptr,size_t count,char persistent);
void *strdup(void *ptr); void *estrdup(void *ptr);void *pestrdup(void *ptr,char persistent);
void free(void *ptr); void efree(void *ptr);
void pefree(void *ptr,char persistent);

  你可能會(huì)注意到,即使是pefree()函數(shù)也要求使用永久性標(biāo)志。這是因?yàn)樵谡{(diào)用pefree()時(shí),它實(shí)際上并不知道是否ptr是一種永久性分配。針對(duì)一個(gè)非永久性分配調(diào)用free()能夠?qū)е码p倍的空間釋放,而針對(duì)一種永久性分配調(diào)用efree()有可能會(huì)導(dǎo)致一個(gè)段錯(cuò)誤,因?yàn)閮?nèi)存管理器會(huì)試圖查找并不存在的管理信息。因此,你的代碼需要記住它分配的數(shù)據(jù)結(jié)構(gòu)是否是永久性的。

  除了分配器函數(shù)核心部分外,還存在其它一些非常方便的ZendMM特定的函數(shù),例如:

void *estrndup(void *ptr,int len);

  該函數(shù)能夠分配len+1個(gè)字節(jié)的內(nèi)存并且從ptr處復(fù)制len個(gè)字節(jié)到最新分配的塊。這個(gè)estrndup()函數(shù)的行為可以大致描述如下:

void *estrndup(void *ptr, int len)
{
 char *dst = emalloc(len + 1);
 memcpy(dst, ptr, len);
 dst[len] = 0;
 return dst;
}

  在此,被隱式放置在緩沖區(qū)最后的NULL字節(jié)可以確保任何使用estrndup()實(shí)現(xiàn)字符串復(fù)制操作的函數(shù)都不需要擔(dān)心會(huì)把結(jié)果緩沖區(qū)傳遞給一個(gè)例如printf()這樣的希望以為NULL為結(jié)束符的函數(shù)。當(dāng)使用estrndup()來(lái)復(fù)制非字符串?dāng)?shù)據(jù)時(shí),最后一個(gè)字節(jié)實(shí)質(zhì)上都浪費(fèi)了,但其中的利明顯大于弊。

void *safe_emalloc(size_t size, size_t count, size_t addtl);
void *safe_pemalloc(size_t size, size_t count,size_t addtl,char persistent);

  這些函數(shù)分配的內(nèi)存空間最終大小是((size*count)+addtl)。你可以會(huì)問(wèn):"為什么還要提供額外函數(shù)呢?為什么不使用一個(gè)emalloc/pemalloc呢?"原因很簡(jiǎn)單:為了安全。盡管有時(shí)候可能性相當(dāng)小,但是,正是這一"可能性相當(dāng)小"的結(jié)果導(dǎo)致宿主平臺(tái)的內(nèi)存溢出。這可能會(huì)導(dǎo)致分配負(fù)數(shù)個(gè)數(shù)的字節(jié)空間,或更有甚者,會(huì)導(dǎo)致分配一個(gè)小于調(diào)用程序要求大小的字節(jié)空間。而safe_emalloc()能夠避免這種類(lèi)型的陷井-通過(guò)檢查整數(shù)溢出并且在發(fā)生這樣的溢出時(shí)顯式地預(yù)以結(jié)束。

  注意,并不是所有的內(nèi)存分配例程都有一個(gè)相應(yīng)的p*對(duì)等實(shí)現(xiàn)。例如,不存在pestrndup(),并且在PHP 5.1版本前也不存在safe_pemalloc()。

  五、 引用計(jì)數(shù)

  慎重的內(nèi)存分配與釋放對(duì)于PHP(它是一種多請(qǐng)求進(jìn)程)的長(zhǎng)期性能有極其重大的影響;但是,這還僅是問(wèn)題的一半。為了使一個(gè)每秒處理上千次點(diǎn)擊的服務(wù)器高效地運(yùn)行,每一次請(qǐng)求都需要使用盡可能少的內(nèi)存并且要盡可能減少不必要的數(shù)據(jù)復(fù)制操作。請(qǐng)考慮下列PHP代碼片斷:

<?php
$a = 'Hello World';
$b = $a;
unset($a);
?>

  在第一次調(diào)用之后,只有一個(gè)變量被創(chuàng)建,并且一個(gè)12字節(jié)的內(nèi)存塊指派給它以便存儲(chǔ)字符串"Hello World",還包括一個(gè)結(jié)尾處的NULL字符?,F(xiàn)在,讓我們來(lái)觀察后面的兩行:$b被置為與變量$a相同的值,然后變量$a被釋放。

  如果PHP因每次變量賦值都要復(fù)制變量?jī)?nèi)容的話,那么,對(duì)于上例中要復(fù)制的字符串還需要復(fù)制額外的12個(gè)字節(jié),并且在數(shù)據(jù)復(fù)制期間還要進(jìn)行另外的處理器加載。這一行為乍看起來(lái)有點(diǎn)荒謬,因?yàn)楫?dāng)?shù)谌写a出現(xiàn)時(shí),原始變量被釋放,從而使得整個(gè)數(shù)據(jù)復(fù)制顯得完全不必要。其實(shí),我們不妨再遠(yuǎn)一層考慮,讓我們?cè)O(shè)想當(dāng)一個(gè)10MB大小的文件的內(nèi)容被裝載到兩個(gè)變量中時(shí)會(huì)發(fā)生什么。這將會(huì)占用20MB的空間,此時(shí),10已經(jīng)足夠了。引擎會(huì)把那么多的時(shí)間和內(nèi)存浪費(fèi)在這樣一種無(wú)用的努力上嗎?

  你應(yīng)該知道,PHP的設(shè)計(jì)者早已深諳此理。

  記住,在引擎中,變量名和它們的值實(shí)際上是兩個(gè)不同的概念。值本身是一個(gè)無(wú)名的zval*存儲(chǔ)體(在本例中,是一個(gè)字符串值),它被通過(guò)zend_hash_add()賦給變量$a。如果兩個(gè)變量名都指向同一個(gè)值,會(huì)發(fā)生什么呢?

{
 zval *helloval;
 MAKE_STD_ZVAL(helloval);
 ZVAL_STRING(helloval, "Hello World", 1);
 zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),&helloval, sizeof(zval*), NULL);
 zend_hash_add(EG(active_symbol_table), "b", sizeof("b"),&helloval, sizeof(zval*), NULL);
}

  此時(shí),你可以實(shí)際地觀察$a或$b,并且會(huì)看到它們都包含字符串"Hello World"。遺憾的是,接下來(lái),你繼續(xù)執(zhí)行第三行代碼"unset($a);"。此時(shí),unset()并不知道$a變量指向的數(shù)據(jù)還被另一個(gè)變量所使用,因此它只是盲目地釋放掉該內(nèi)存。任何隨后的對(duì)變量$b的存取都將被分析為已經(jīng)釋放的內(nèi)存空間并因此導(dǎo)致引擎崩潰。

  這個(gè)問(wèn)題可以借助于zval(它有好幾種形式)的第四個(gè)成員refcount加以解決。當(dāng)一個(gè)變量被首次創(chuàng)建并賦值時(shí),它的refcount被初始化為1,因?yàn)樗患俣▋H由最初創(chuàng)建它時(shí)相應(yīng)的變量所使用。當(dāng)你的代碼片斷開(kāi)始把helloval賦給$b時(shí),它需要把refcount的值增加為2;這樣以來(lái),現(xiàn)在該值被兩個(gè)變量所引用:

{
 zval *helloval;
 MAKE_STD_ZVAL(helloval);
 ZVAL_STRING(helloval, "Hello World", 1);
 zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),&helloval, sizeof(zval*), NULL);
 ZVAL_ADDREF(helloval);
 zend_hash_add(EG(active_symbol_table), "b", sizeof("b"),&helloval,sizeof(zval*),NULL);
}

  現(xiàn)在,當(dāng)unset()刪除原變量的$a相應(yīng)的副本時(shí),它就能夠從refcount參數(shù)中看到,還有另外其他人對(duì)該數(shù)據(jù)感興趣;因此,它應(yīng)該只是減少refcount的計(jì)數(shù)值,然后不再管它。

  六、 寫(xiě)復(fù)制(Copy on Write)

  通過(guò)refcounting來(lái)節(jié)約內(nèi)存的確是不錯(cuò)的主意,但是,當(dāng)你僅想改變其中一個(gè)變量的值時(shí)情況會(huì)如何呢?為此,請(qǐng)考慮下面的代碼片斷:

<?php
$a = 1;
$b = $a;
$b += 5;
?>

  通過(guò)上面的邏輯流程,你當(dāng)然知道$a的值仍然等于1,而$b的值最后將是6。并且此時(shí),你還知道,Zend在盡力節(jié)省內(nèi)存-通過(guò)使$a和$b都引用相同的zval(見(jiàn)第二行代碼)。那么,當(dāng)執(zhí)行到第三行并且必須改變$b變量的值時(shí),會(huì)發(fā)生什么情況呢?

  回答是,Zend要查看refcount的值,并且確保在它的值大于1時(shí)對(duì)之進(jìn)行分離。在Zend引擎中,分離是破壞一個(gè)引用對(duì)的過(guò)程,正好與你剛才看到的過(guò)程相反:

zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC)
{
 zval **varval, *varcopy;
 if (zend_hash_find(EG(active_symbol_table),varname, varname_len + 1, (void**)&varval) == FAILURE) {
  /* 變量根本并不存在-失敗而導(dǎo)致退出*/
  return NULL;
 }
 if ((*varval)->refcount < 2) {
  /* varname是唯一的實(shí)際引用,
  *不需要進(jìn)行分離
  */
  return *varval;
 }
 /* 否則,再?gòu)?fù)制一份zval*的值*/
 MAKE_STD_ZVAL(varcopy);
 varcopy = *varval;
 /* 復(fù)制任何在zval*內(nèi)的已分配的結(jié)構(gòu)*/
 zval_copy_ctor(varcopy);
 /*刪除舊版本的varname
 *這將減少該過(guò)程中varval的refcount的值
 */
 zend_hash_del(EG(active_symbol_table), varname, varname_len + 1);
 /*初始化新創(chuàng)建的值的引用計(jì)數(shù),并把它依附到
 * varname變量
 */
 varcopy->refcount = 1;
 varcopy->is_ref = 0;
 zend_hash_add(EG(active_symbol_table), varname, varname_len + 1,&varcopy, sizeof(zval*), NULL);
 /*返回新的zval* */
 return varcopy;
}

  現(xiàn)在,既然引擎有一個(gè)僅為變量$b所擁有的zval*(引擎能知道這一點(diǎn)),所以它能夠把這個(gè)值轉(zhuǎn)換成一個(gè)long型值并根據(jù)腳本的請(qǐng)求給它增加5。

  七、 寫(xiě)改變(change-on-write)

  引用計(jì)數(shù)概念的引入還導(dǎo)致了一個(gè)新的數(shù)據(jù)操作可能性,其形式從用戶(hù)空間腳本管理器看來(lái)與"引用"有一定關(guān)系。請(qǐng)考慮下列的用戶(hù)空間代碼片斷:

<?php
$a = 1;
$b = &$a;
$b += 5;
?>

  在上面的PHP代碼中,你能看出$a的值現(xiàn)在為6,盡管它一開(kāi)始為1并且從未(直接)發(fā)生變化。之所以會(huì)發(fā)生這種情況是因?yàn)楫?dāng)引擎開(kāi)始把$b的值增加5時(shí),它注意到$b是一個(gè)對(duì)$a的引用并且認(rèn)為"我可以改變?cè)撝刀槐胤蛛x它,因?yàn)槲蚁胧顾械囊米兞慷寄芸吹竭@一改變"。

  但是,引擎是如何知道的呢?很簡(jiǎn)單,它只要查看一下zval結(jié)構(gòu)的第四個(gè)和最后一個(gè)元素(is_ref)即可。這是一個(gè)簡(jiǎn)單的開(kāi)/關(guān)位,它定義了該值是否實(shí)際上是一個(gè)用戶(hù)空間風(fēng)格引用集的一部分。在前面的代碼片斷中,當(dāng)執(zhí)行第一行時(shí),為$a創(chuàng)建的值得到一個(gè)refcount為1,還有一個(gè)is_ref值為0,因?yàn)樗鼉H為一個(gè)變量($a)所擁有并且沒(méi)有其它變量對(duì)它產(chǎn)生寫(xiě)引用改變。在第二行,這個(gè)值的refcount元素被增加為2,除了這次is_ref元素被置為1之外(因?yàn)槟_本中包含了一個(gè)"&"符號(hào)以指示是完全引用)。

  最后,在第三行,引擎再一次取出與變量$b相關(guān)的值并且檢查是否有必要進(jìn)行分離。這一次該值沒(méi)有被分離,因?yàn)榍懊鏇](méi)有包括一個(gè)檢查。下面是get_var_and_separate()函數(shù)中與refcount檢查有關(guān)的部分代碼:

if ((*varval)->is_ref || (*varval)->refcount < 2) {
 /* varname是唯一的實(shí)際引用,
 * 或者它是對(duì)其它變量的一個(gè)完全引用
 *任何一種方式:都沒(méi)有進(jìn)行分離
 */
 return *varval;
}

  這一次,盡管refcount為2,卻沒(méi)有實(shí)現(xiàn)分離,因?yàn)檫@個(gè)值是一個(gè)完全引用。引擎能夠自由地修改它而不必關(guān)心其它變量值的變化。

  八、 分離問(wèn)題

  盡管已經(jīng)存在上面討論到的復(fù)制和引用技術(shù),但是還存在一些不能通過(guò)is_ref和refcount操作來(lái)解決的問(wèn)題。請(qǐng)考慮下面這個(gè)PHP代碼塊:

<?php
$a = 1;
$b = $a;
$c = &$a;
?>

  在此,你有一個(gè)需要與三個(gè)不同的變量相關(guān)聯(lián)的值。其中,兩個(gè)變量是使用了"change-on-write"完全引用方式,而第三個(gè)變量處于一種可分離的"copy-on-write"(寫(xiě)復(fù)制)上下文中。如果僅使用is_ref和refcount來(lái)描述這種關(guān)系,有哪些值能夠工作呢?

  回答是:沒(méi)有一個(gè)能工作。在這種情況下,這個(gè)值必須被復(fù)制到兩個(gè)分離的zval*中,盡管兩者都包含完全相同的數(shù)據(jù)(見(jiàn)圖2)。

深入探討PHP中的內(nèi)存管理問(wèn)題
圖2.引用時(shí)強(qiáng)制分離


  同樣,下列代碼塊將引起相同的沖突并且強(qiáng)迫該值分離出一個(gè)副本(見(jiàn)圖3)。

深入探討PHP中的內(nèi)存管理問(wèn)題
圖3.復(fù)制時(shí)強(qiáng)制分離


<?php
$a = 1;
$b = &$a;
$c = $a;
?>

  注意,在這里的兩種情況下,$b都與原始的zval對(duì)象相關(guān)聯(lián),因?yàn)樵诜蛛x發(fā)生時(shí)引擎無(wú)法知道介于到該操作當(dāng)中的第三個(gè)變量的名字。

  九、 總結(jié)

  PHP是一種托管語(yǔ)言。從普通用戶(hù)角度來(lái)看,這種仔細(xì)地控制資源和內(nèi)存的方式意味著更為容易地進(jìn)行原型開(kāi)發(fā)并導(dǎo)致出現(xiàn)更少的沖突。然而,當(dāng)我們深入"內(nèi)里"之后,一切的承諾似乎都不復(fù)存在,最終還要依賴(lài)于真正有責(zé)任心的開(kāi)發(fā)者來(lái)維持整個(gè)運(yùn)行時(shí)刻環(huán)境的一致性。

相關(guān)文章

  • PHP 存儲(chǔ)文本換行實(shí)現(xiàn)方法

    PHP 存儲(chǔ)文本換行實(shí)現(xiàn)方法

    在文本存儲(chǔ)時(shí)使用\n如果發(fā)現(xiàn)沒(méi)有效果, 這時(shí)可以使用\r\n就可以了,希望對(duì)有需要的朋友有所幫助。
    2010-01-01
  • php下嘗試使用GraphicsMagick的縮略圖功能

    php下嘗試使用GraphicsMagick的縮略圖功能

    現(xiàn)在,對(duì)一個(gè)Web程序員來(lái)說(shuō),圖像處理已經(jīng)屬于必會(huì)知識(shí)之一了。且不說(shuō)Flickr,Yupoo等專(zhuān)業(yè)圖片分享網(wǎng)站,就算是一個(gè)和圖片分享不沾邊的網(wǎng)站,也會(huì)用到很多圖片處理的功能,比如說(shuō):用戶(hù)上傳頭像,然后自動(dòng)生成縮略圖。
    2011-01-01
  • PHP序列化操作方法分析

    PHP序列化操作方法分析

    這篇文章主要介紹了PHP序列化操作方法,結(jié)合實(shí)例形式分析了php序列化的原理、實(shí)現(xiàn)方法、相關(guān)函數(shù)與操作技巧,需要的朋友可以參考下
    2016-09-09
  • PHP 實(shí)現(xiàn)公歷日期與農(nóng)歷日期的互轉(zhuǎn)換

    PHP 實(shí)現(xiàn)公歷日期與農(nóng)歷日期的互轉(zhuǎn)換

    這篇文章主要介紹了PHP 實(shí)現(xiàn)公歷日期與農(nóng)歷日期的互轉(zhuǎn)換的相關(guān)資料,希望通過(guò)本文大家能幫助到大家,需要的朋友可以參考下
    2017-09-09
  • php遠(yuǎn)程下載類(lèi)分享

    php遠(yuǎn)程下載類(lèi)分享

    這篇文章主要為大家分享了php遠(yuǎn)程下載類(lèi),用戶(hù)可以將下載文件到主機(jī),感興趣的小伙伴們可以參考一下
    2016-04-04
  • 使用圖靈api創(chuàng)建微信聊天機(jī)器人

    使用圖靈api創(chuàng)建微信聊天機(jī)器人

    本文給大家主要介紹的是利用圖靈機(jī)器人分分鐘搭建自己的微信聊天機(jī)器人,方法十分的簡(jiǎn)單,有需要的小伙伴可以參考下。
    2015-07-07
  • PHP中繪制圖像的一些函數(shù)總結(jié)

    PHP中繪制圖像的一些函數(shù)總結(jié)

    這篇文章主要介紹了PHP中繪制圖像的一些函數(shù)總結(jié),本文講解了如繪制點(diǎn)和線、繪制矩形、繪制多邊形、繪制橢圓、繪制弧線等功能,需要的朋友可以參考下
    2014-11-11
  • PHP中信息格式化操作詳解(MessageFormatter類(lèi))

    PHP中信息格式化操作詳解(MessageFormatter類(lèi))

    這篇文章主要給大家介紹了關(guān)于PHP中信息格式化操作的相關(guān)資料,主要運(yùn)用的是專(zhuān)門(mén)用于信息格式化的MessageFormatter類(lèi),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-07-07
  • php實(shí)現(xiàn)驗(yàn)證郵箱格式的代碼實(shí)例

    php實(shí)現(xiàn)驗(yàn)證郵箱格式的代碼實(shí)例

    在本篇文章里小編給大家整理的是關(guān)于php實(shí)現(xiàn)驗(yàn)證郵箱格式的代碼實(shí)例以及相關(guān)知識(shí)點(diǎn),需要的朋友們參考下。
    2020-01-01
  • php利用ffmpeg提取視頻中音頻與視頻畫(huà)面的方法詳解

    php利用ffmpeg提取視頻中音頻與視頻畫(huà)面的方法詳解

    想要提取視頻中的音頻信息,首選的技術(shù)是ffmpeg,ffmpeg是一個(gè)非常有用的命令行程序,它可以用來(lái)轉(zhuǎn)碼媒體文件。這篇文章主要給大家介紹了PHP利用ffmpeg提取視頻中音頻與視頻畫(huà)面的相關(guān)資料,需要的朋友可以參考下。
    2017-06-06

最新評(píng)論