php
foreach雖然簡單
下面列舉了幾種case
問題
$arr = array(
foreach($arr as $k => &$v) {
$v = $v *
}
// now $arr is array(
foreach($arr as $k => $v) {
echo "$k"
}
先從簡單的開始
為何不是
其實
foreach($arr as $k => $v){
//在用戶代碼執行之前隱含了
$v = currentVal();
$k = currentKey();
//繼續運行用戶代碼
……
}
根據上述理論
第
第
第
隨後代碼進入了第二個foreach
第
第
第
OK
如何解決類似問題呢?php手冊上有一段提醒
Warning : 數組最後一個元素的 $value 引用在 foreach 循環之後仍會保留
$arr = array(
foreach($arr as $k => &$v) {
$v = $v *
}
unset($v);
foreach($arr as $k => $v) {
echo "$k"
}
// 輸出
從這個問題中我們可以看出
問題
$arr = array(
foreach($arr as $k => $v) {
echo key($arr)
}
// 打印
這個問題更加詭異
那為何key($arr)一直是
先用vld查看編譯之後的opcode:
我們從第
由 於$arr為CV
static int ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zend_free_op free_op
zval *value = _get_zval_ptr_tmp(&opline
// CV數組中創建出$arr**指針
zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline
if (IS_CV == IS_VAR && !variable_ptr_ptr) {
……
}
else {
// 將array賦值給$arr
value = zend_assign_to_variable(variable_ptr_ptr
if (!RETURN_VALUE_UNUSED(&opline
AI_SET_PTR(EX_T(opline
PZVAL_LOCK(value);
}
}
ZEND_VM_NEXT_OPCODE();
}
ASSIGN指令完成之後
接下來執行數組的循環操作
static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
……
if (……) {
……
} else {
// 通過CV數組獲取指向array的指針
array_ptr = _get_zval_ptr_cv(&opline
……
}
……
// 將指向array的指針保存到zend_execute_data
AI_SET_PTR(EX_T(opline
PZVAL_LOCK(array_ptr);
if (iter) {
……
} else if ((fe_ht = HASH_OF(array_ptr)) != NULL) {
// 重置數組內部指針
zend_hash_internal_pointer_reset(fe_ht);
if (ce) {
……
}
is_empty = zend_hash_has_more_elements(fe_ht) != SUCCESS;
// 設置EX_T(opline
zend_hash_get_pointer(fe_ht
} else {
……
}
……
}
這裡主要將
•EX_T(opline
•EX_T(opline
FE_RESET指令執行完畢之後
接下來我們繼續查看FE_FETCH
static int ZEND_FASTCALL ZEND_FE_FETCH_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
// 注意指針是從EX_T(opline
zval *array = EX_T(opline
……
switch (zend_iterator_unwrap(array
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
// 此處獲取該指針
zend_hash_set_pointer(fe_ht
// 獲取元素的值
if (zend_hash_get_current_data(fe_ht
ZEND_VM_JMP(EX(op_array)
}
if (use_key) {
key_type = zend_hash_get_current_key_ex(fe_ht
}
// 數組內部指針移動到下一個元素
zend_hash_move_forward(fe_ht);
// 移動之後的指針保存到EX_T(opline
zend_hash_get_pointer(fe_ht
break;
case ZEND_ITER_OBJECT:
……
}
……
}
根據FE_FETCH的實現
簡單來說
那為何會輸出
我們繼續看第
查閱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
……
// 變量分離
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的主要作用為
注意
接下來的循環就不一一贅述了
•foreach結構使用的是下方藍色的array
•key
至此我們明白了為何key和current一直返回array的第二個元素
問題
$arr = array(
foreach($arr as $k => &$v) {
echo key($arr)
}// 打印
本題與問題
首先foreach會調用FE_RESET:
static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
……
if (opline
// 從CV中獲取變量
array_ptr_ptr = _get_zval_ptr_ptr_cv(&opline
if (array_ptr_ptr == NULL || array_ptr_ptr == &EG(uninitialized_zval_ptr)) {
……
}
else if (Z_TYPE_PP(array_ptr_ptr) == IS_OBJECT) {
……
}
else {
// 針對遍歷array的情況
if (Z_TYPE_PP(array_ptr_ptr) == IS_ARRAY) {
SEPARATE_ZVAL_IF_NOT_REF(array_ptr_ptr);
if (opline
// 將保存array的zval設置為is_ref
Z_SET_ISREF_PP(array_ptr_ptr);
}
}
array_ptr = *array_ptr_ptr;
Z_ADDREF_P(array_ptr);
}
} else {
……
}
……
}
問題
最終
接下來分析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
……
// 變量分離
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僅僅分離is_ref=false的變量
上圖解釋了前
ZEND_API int zend_hash_move_forward_ex(HashTable *ht
{
HashPosition *current = pos ? pos : &ht
IS_CONSISTENT(ht);
if (*current) {
*current = (*current)
return SUCCESS;
} else
return FAILURE;
}
由於此時內部指針已經指向了數組的最後一個元素
問題
$arr = array(
$tmp = $arr;
foreach($tmp as $k => &$v){
$v *=
}
var_dump($arr
該題與foreach關系不大
代碼裡首先創建了數組$arr
為什麼呢?
這是由於在php中
題外話
class A{
public $foo =
}
$a
$a
echo $a
回到題目中的代碼
也許有同學會疑問
幸好php有更聰明的處理辦法
static inline zval* zend_assign_to_variable(zval **variable_ptr_ptr
{
zval *variable_ptr = *variable_ptr_ptr;
zval garbage;
……
// 左值為object類型
if (Z_TYPE_P(variable_ptr) == IS_OBJECT && Z_OBJ_HANDLER_P(variable_ptr
……
}
// 左值為引用的情況
if (PZVAL_IS_REF(variable_ptr)) {
……
} else {
// 左值refcount__gc=
if (Z_DELREF_P(variable_ptr)==
……
} else {
GC_ZVAL_CHECK_POSSIBLE_ROOT(*variable_ptr_ptr);
// 非臨時變量
if (!is_tmp_var) {
if (PZVAL_IS_REF(value) && Z_REFCOUNT_P(value) >
ALLOC_ZVAL(variable_ptr);
*variable_ptr_ptr = variable_ptr;
*variable_ptr = *value;
Z_SET_REFCOUNT_P(variable_ptr
zval_copy_ctor(variable_ptr);
} else {
// $tmp=$arr會運行到這裡
// value為指向$arr裡實際array數據的指針
// 僅僅是復制指針
*variable_ptr_ptr = value;
// value的refcount__gc值+
Z_ADDREF_P(value);
}
} else {
……
}
}
Z_UNSET_ISREF_PP(variable_ptr_ptr);
}
return *variable_ptr_ptr;
}
可見$tmp = $arr的本質就是將array的指針進行復制
既然只有一份array
繼續看PHP源碼中的ZEND_FE_RESET_SPEC_CV_HANDLER函數
static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zval *array_ptr
HashTable *fe_ht;
zend_object_iterator *iter = NULL;
zend_class_entry *ce = NULL;
zend_bool is_empty =
// 對變量進行FE_RESET
if (opline
array_ptr_ptr = _get_zval_ptr_ptr_cv(&opline
if (array_ptr_ptr == NULL || array_ptr_ptr == &EG(uninitialized_zval_ptr)) {
……
}
// foreach一個object
else if (Z_TYPE_PP(array_ptr_ptr) == IS_OBJECT) {
……
}
else {
// 本例會進入該分支
if (Z_TYPE_PP(array_ptr_ptr) == IS_ARRAY) {
// 注意此處的SEPARATE_ZVAL_IF_NOT_REF
// 它會重新復制一個數組出來
// 真正分離$tmp和$arr
SEPARATE_ZVAL_IF_NOT_REF(array_ptr_ptr);
if (opline
Z_SET_ISREF_PP(array_ptr_ptr);
}
}
array_ptr = *array_ptr_ptr;
Z_ADDREF_P(array_ptr);
}
} else {
……
}
// 重置數組內部指針
……
}
從代碼中可以看出
FE_RESET之後
上 圖解釋了為何foreach並不會對原來的$arr產生影響
From:http://tw.wingwit.com/Article/program/PHP/201311/20993.html