深入解析php中的foreach問題(2)_PHP教程
推薦:PHP做好防盜鏈的設置方法盜鏈 是指服務提供商自己不提供服務的內容,通過技術手段繞過其它有利益的最終用戶界面(如廣告),直接在自己的 網站上向最終用戶提供其它服務提供商的服務內容,騙取最終用戶的瀏覽和點擊率。受益者不提供資源或提供很少的資源,而真正的服務提供商卻得不到任何的收 益
這里主要將2個重要的指針存入了zend_execute_data->Ts中:
•EX_T(opline->result.u.var).var ---- 指向array的指針
•EX_T(opline->result.u.var).fe.fe_pos ---- 指向array內部元素的指針
FE_RESET指令執行完畢之后,內存中實際情況如下:
接下來我們繼續查看FE_FETCH,它對應的執行函數為ZEND_FE_FETCH_SPEC_VAR_HANDLER:
static int ZEND_FASTCALL ZEND_FE_FETCH_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
// 注意指針是從EX_T(opline->op1.u.var).var.ptr獲取的
zval *array = EX_T(opline->op1.u.var).var.ptr;
……
switch (zend_iterator_unwrap(array, &iter TSRMLS_CC)) {
default:
case ZEND_ITER_INVALID:
……
case ZEND_ITER_PLAIN_OBJECT: {
……
}
case ZEND_ITER_PLAIN_ARRAY:
fe_ht = HASH_OF(array);
// 特別注意:
// FE_RESET指令中將數組內部元素的指針保存在EX_T(opline->op1.u.var).fe.fe_pos
// 此處獲取該指針
zend_hash_set_pointer(fe_ht, &EX_T(opline->op1.u.var).fe.fe_pos);
// 獲取元素的值
if (zend_hash_get_current_data(fe_ht, (void **) &value)==FAILURE) {
ZEND_VM_JMP(EX(op_array)->opcodes+opline->op2.u.opline_num);
}
if (use_key) {
key_type = zend_hash_get_current_key_ex(fe_ht, &str_key, &str_key_len, &int_key, 1, NULL);
}
// 數組內部指針移動到下一個元素
zend_hash_move_forward(fe_ht);
// 移動之后的指針保存到EX_T(opline->op1.u.var).fe.fe_pos
zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.u.var).fe.fe_pos);
break;
case ZEND_ITER_OBJECT:
……
}
……
}
根據FE_FETCH的實現,我們大致上明白了foreach($arr as $k => $v)所做的事情。它會根據zend_execute_data->Ts的指針去獲取數組元素,在獲取成功之后,將該指針移動到下一個位置再重新保存。
簡單來說,由于第一遍循環中FE_FETCH中已經將數組的內部指針移動到了第二個元素,所以在foreach內部調用key($arr)和current($arr)時,實際上獲取的便是1和'b'。
那為何會輸出3遍1=>b呢?
我們繼續看第9行和第13行的SEND_REF指令,它表示將$arr參數壓棧。緊接著一般會使用DO_FCALL指令去調用key和current函數。PHP并非被編譯成本地機器碼,因此php采用這樣的opcode指令去模擬實際CPU和內存的工作方式。
查閱PHP源碼中的SEND_REF:
static int ZEND_FASTCALL ZEND_SEND_REF_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
……
// 從CV中獲取$arr指針的指針
varptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC);
……
// 變量分離,此處重新copy了一份array專門用于key函數
SEPARATE_ZVAL_TO_MAKE_IS_REF(varptr_ptr);
varptr = *varptr_ptr;
Z_ADDREF_P(varptr);
// 壓棧
zend_vm_stack_push(varptr TSRMLS_CC);
ZEND_VM_NEXT_OPCODE();
}
上述代碼中的SEPARATE_ZVAL_TO_MAKE_IS_REF是一個宏:
#define SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv) \
if (!PZVAL_IS_REF(*ppzv)) { \
SEPARATE_ZVAL(ppzv); \
Z_SET_ISREF_PP((ppzv)); \
}
SEPARATE_ZVAL_TO_MAKE_IS_REF的主要作用為,如果變量不是一個引用,則在內存中copy出一份新的。本例中它將array('a','b','c')復制了一份。因此變量分離之后的內存為:
注意,變量分離完成之后,CV數組中的指針指向了新copy出來的數據,而通過zend_execute_data->Ts中的指針則依然可以獲取舊的數據。
接下來的循環就不一一贅述了,結合上圖來說:
•foreach結構使用的是下方藍色的array,會依次遍歷a,b,c
•key、current使用的是上方黃色的array,它的內部指針永遠指向b
至此我們明白了為何key和current一直返回array的第二個元素,由于沒有外部代碼作用于copy出來的array,它的內部指針便永遠不會移動。
問題3:
$arr = array('a','b','c');
foreach($arr as $k => &$v) {
echo key($arr), '=>', current($arr);
}// 打印 1=>b 2=>c =>
分享:如何使用PHP實現javascript的escape和unescape函數前端開發工程師都知道javascript有編碼函數escape()和對應的解碼函數unescape(),而php中只有個urlencode和 urldecode,這個編碼和解碼函數對encodeURI和encodeURIComponent有效,但是對escape的是無效的。 javascript中的escape()函數和unescape()函數用戶字符串編碼
- 相關鏈接:
- 教程說明:
PHP教程-深入解析php中的foreach問題(2)。