Ceci est mon code:
$a = 5;
$b = &$a;
echo ++$a.$b++;
Ne devrait-il pas imprimer 66?
Pourquoi imprime-t-il 76?
Ceci est mon code:
$a = 5;
$b = &$a;
echo ++$a.$b++;
Ne devrait-il pas imprimer 66?
Pourquoi imprime-t-il 76?
Alright. C'est en fait assez simple de comportement, et il a à voir avec la façon dont les références travailler en PHP. Il n'est pas un bug, mais un comportement inattendu.
PHP utilise en interne de copie sur écriture. Ce qui signifie que les variables internes sont copiées lorsque vous écrivez pour eux (donc $a = $b;
n'est pas copier la mémoire jusqu'à ce que vous modifiez en fait l'un d'entre eux). Avec des références, il n'a jamais fait des copies. C'est important pour plus tard.
Regardons ces opcodes:
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > ASSIGN !0, 5
3 1 ASSIGN_REF !1, !0
4 2 PRE_INC $2 !0
3 POST_INC ~3 !1
4 CONCAT ~4 $2, ~3
5 ECHO ~4
6 > RETURN 1
Les deux premiers devraient être assez faciles à comprendre.
5
dans le compilé variable nommée !0
.!0
de !1
(la direction n'a pas d'importance)Jusqu'à présent, c'est simple. Maintenant vient le morceau intéressant:
$2
.Alors, regardons le code source derrière PRE_INC
lorsqu'elle est appelée avec une variable:
static int ZEND_FASTCALL ZEND_PRE_INC_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_free_op free_op1;
zval **var_ptr;
SAVE_OPLINE();
var_ptr = _get_zval_ptr_ptr_var(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (IS_VAR == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
}
if (IS_VAR == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(&EG(uninitialized_zval));
AI_SET_PTR(&EX_T(opline->result.var), &EG(uninitialized_zval));
}
if (free_op1.var) {zval_ptr_dtor(&free_op1.var);};
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
&& Z_OBJ_HANDLER_PP(var_ptr, get)
&& Z_OBJ_HANDLER_PP(var_ptr, set)) {
/* proxy object */
zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
Z_ADDREF_P(val);
fast_increment_function(val);
Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
zval_ptr_dtor(&val);
} else {
fast_increment_function(*var_ptr);
}
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(*var_ptr);
AI_SET_PTR(&EX_T(opline->result.var), *var_ptr);
}
if (free_op1.var) {zval_ptr_dtor(&free_op1.var);};
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
Maintenant, je ne vous attendez pas à comprendre ce qui fait tout de suite (c'est profond moteur de vaudou), mais nous allons marcher à travers elle.
Les deux premiers si les déclarations de vérifier pour voir si la variable est "safe" pour incrémenter (le vérifie d'abord si c'est un objet surchargé, la deuxième vérifie si la variable est la variable d'erreur $php_error
).
La prochaine est vraiment le morceau intéressant pour nous. Depuis que nous sommes à la modification de la valeur, il doit préforme de copie sur écriture. Donc il appelle:
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
Maintenant, rappelez-vous, nous avons déjà défini la variable de référence ci-dessus. Si la variable n'est pas séparé... ce Qui signifie tout ce que nous faisons ici adviendra $b
ainsi...
Ensuite, la variable est incrémentée (fast_increment_function()
).
Enfin, il définit le résultat que lui-même. C'est la copie sur écriture de nouveau. Ce n'est pas de retourner la valeur de l'opération, mais la variable. Donc, ce PRE_INC
retours est toujours une référence à l' $a
et $b
.
PRE_INC
, sauf pour un fait TRÈS important.Nous allons vérifier le code source de nouveau:
static int ZEND_FASTCALL ZEND_POST_INC_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
retval = &EX_T(opline->result.var).tmp_var;
ZVAL_COPY_VALUE(retval, *var_ptr);
zendi_zval_copy_ctor(*retval);
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
fast_increment_function(*var_ptr);
}
Cette fois, j'ai coupé tous les non-choses intéressantes. Alors, regardons ce qu'il fait.
Tout d'abord, il obtient le retour à la variable temporaire (~3
dans notre code ci-dessus).
Puis il copie la valeur de son argument (!1
ou $b
) dans le résultat (et donc la référence est cassé).
Puis il incrémente l'argument.
Maintenant, rappelez-vous, l'argument !1
est la variable $b
, ce qui a une référence à l' !0
($a
) et $2
, qui, si vous vous en souvenez, était le résultat de l' PRE_INC
.
Donc là vous l'avez. Il retourne 76 parce que la référence est maintenue dans PRE_INC du résultat.
Nous pouvons le prouver en forçant une copie, par l'attribution de la pré-inc à une variable temporaire première (par l'attribution normale, qui va casser la référence):
$a = 5;
$b = &$a;
$c = ++$a;
$d = $b++;
echo $c.$d;
Qui fonctionne comme prévu. La preuve
Et on peut reproduire le comportement d'autres (bug) par l'introduction d'une fonction de maintenir la référence:
function &pre_inc(&$a) {
return ++$a;
}
$a = 5;
$b = &$a;
$c = &pre_inc($a);
$d = $b++;
echo $c.$d;
Qui fonctionne comme vous allez le voir (76): la Preuve
Remarque: la seule raison pour la fonction séparée ici, c'est que PHP analyseur n'aime pas $c = &++$a;
. Nous avons donc besoin d'ajouter un niveau d'indirection par le biais de l'appel de la fonction pour le faire...
La raison pour laquelle je ne pas considérer cela comme un bug, c'est que c'est la façon dont les références sont censés travailler. Pré-incrémentation d'une variable en question sera de retour cette variable. Même un non-référencé variable doit retourner cette variable. Il peut ne pas être ce que vous attendez ici, mais il fonctionne très bien dans presque tous les autres cas...
Si vous utilisez des références, vous le faites mal à propos de 99% du temps. Donc, ne pas utiliser de références, sauf si vous avez absolument besoin d'eux. PHP est beaucoup plus intelligente que vous pouvez penser à des optimisations de mémoire. Et votre utilisation de références vraiment entrave la façon dont il fonctionne. Ainsi, alors que vous pensez peut-être écrit smart code, vous allez vraiment avoir à écrire de moins en moins efficace et moins respectueux du code de la vaste majorité du temps...
Et si vous voulez en savoir plus sur les Références et comment des variables en PHP, la caisse Une De Mes Vidéos sur YouTube sur le sujet...
Je pense que la ligne de concaténation complète est d'abord exécutée puis envoyée avec la fonction echo. Par exemple
$a = 5;
$b = &$a;
echo ++$a.$b++;
// output 76
$a = 5;
$b = &$a;
echo ++$a;
echo $b++;
// output 66
EDIT: Également très important, $ b est égal à 7, mais fait écho avant d’ajouter:
$a = 5;
$b = &$a;
echo ++$a.$b++; //76
echo $b;
// output 767
EDIT: ajout de Corbin exemple: https://eval.in/34067
Il y a évidemment un bug dans PHP. Si vous exécutez ce code:
<?php
{
$a = 5;
echo ++$a.$a++;
}
echo "\n";
{
$a = 5;
$b = &$a;
echo ++$a.$b++;
}
echo "\n";
{
$a = 5;
echo ++$a.$a++;
}
Vous obtenez:
66 76 76
Ce qui signifie que le même bloc (le 1er et le 3ème sont identiques) de code n'est pas toujours le même résultat. Apparemment, la référence et l'incrément sont en train de mettre du PHP dans un faux de l'état.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.