Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Странный PHP

А вот ПХП, в отличие от ДжаваСкрипта действительно странный. Давайте посмотрим на такой вот код и его результат:

$ php -a
Interactive shell

php > $a = 1; echo $a + $a++;
3
php > $a = 1; echo $a + $a + $a++;
3

Как видите, в том и другом случае у нас один результат — «3». Даже первая «тройка», казалось бы, противоречит здравому смыслу, а вторая — тем более. Что же происходит? Давайте разбираться.

Как работает первый пример?

Операция сложения левоассоциативна — разбор агрументов начинается слева направо. Двигаясь таким образом, парсер видит выражение в котором две операции — сложение и постинкремент, у постинкремента приоритет выше, поэтому вычисляется сначала он — возвращая в качестве значения «1» и увеличивая переменную на единицу, потом вычисляется операция сложения, складывая полученную единицу с двойкой (так как постинкремент увеличил значение переменной на единицу). Получается «три».

Похожим образом обрабатывается умножение вместе со сложением: 2 + 2 * 2 = 6, а не 8, потому что умножение имеет более высокий приоритет.

Во втором случае всё происходит похожим образом, но чуть иначе — парсер, обрабатывая левоассоциативное сложение, берёт первые два аргумента, складывает их, получает «двойку», двигается дальше, видит двойку, сложение и постинкремент переменной. Постинкремент более приоритетный, он его вычисляет раньше, возвращая в сложение «единицу», значение переменной увеличивается, но его уже никто не использует — складываются числа «2» (от предыдущего сложения) и «1» (вернул постинкремент). Получается «три».

В байт-кодах всё перечисленное выглядит следующим образом:

Байт-коды (23.90КиБ)

Тут с восьмой строки начинается второй пример (правда присваивание единицы во втором примере я опустил — как видите второй операции ASSIGN нет).

12 комментариев
Артём Зорин 2015

Похоже на int i = 5; i = ++i + ++i;

Евгений Степанищев (bolknote.ru) 2015

Комментарий для Артём Зорин:

Разве?

Это ведь Си? Я скомпилировал, получил ожидаемый ответ — 13. Что тут неожиданного? 6 + 7 = 13.

Василий Топоров 2015

Комментарий для Евгения Степанищева:

Всё равно не могу понять, почему во втором примере постинкремент вернул 1.

«значение переменной увеличивается, но его уже никто не использует — складываются числа „2“ (от предыдущего сложения) и „1“ (вернул постинкремент)»

Т. е. после сложения, когда начинает выполняться постинкремент, что для него лежит в $a? Там ведь так и осталась единица? Почему он её не увеличивает на 1 как в первом примере?

Алекс 2015

Комментарий для Василий Топоров:

Поддерживаю, мне тоже это не понятно

Евгений Степанищев (bolknote.ru) 2015

Комментарий для Василий Топоров:

Всё равно не могу понять, почему во втором примере постинкремент вернул 1.

Потому что постинкремент всегда возвращает текущее значение (т. е. $a, а там 1), он так устроен — возвращает значение, потом увеличивает переменную на единицу.

Василий Топоров 2015

Комментарий для Василий Топоров:

«Потому что постинкремент всегда возвращает текущее значение (т. е. $a, а там 1), он так устроен — возвращает значение, потом увеличивает переменную на единицу.»
Фактически получается, что во втором примере постинкремент не выполняется.
Ещё вопрос, если позволите: почему этот пример выводит 5? Разве не должны скобки установить приоритет выполнения?
php > $a = 1; echo $a + ($a + $a++);
5

timofei 2015

Комментарий для Василий Топоров:

php > $a = 1; echo $a + ($a + $a++);
5

тут так — ($a + $a++) — первым считается $a++ — он выдает 1 и меняет $a на 2 и результат складывает = 3.

$a + (3) — $a уже два после $a++ и плюс 3 получаем 5

все верно

timofei 2015

а так, да ПХП местами странный.

Dmitry 2015

Что-то не могу понять логики объяснения, почему в первом примере мы НЕ суммируем возвращаемую постинкрементом 1, а во втором примере суммируем ?
По-моему мы пытаемся оправдать косяк php
в С поведение в первом и втором примере одинаково:
#include <stdio.h>
int main()
{
    int a = 1;
    printf(«%d\n», a + a++);
    a = 1;
    printf(«%d\n»,a + a + a++);
    return 0;
}

результат:
2
3

Евгений Степанищев (bolknote.ru) 2015

Комментарий для Dmitry:

Ну так перечитайте ещё раз, я не знаю как ещё понятнее объяснить.

Dmitry 2015

Следуя логике второго примера применительно к первому:
Парсер, обрабатывая левоассоциативное сложение, берёт первый аргумент, 1, двигается дальше, видит сложение и постинкремент переменной. Постинкремент более приоритетный, он его вычисляет раньше, возвращая в сложение «единицу», значение переменной увеличивается, но его уже никто не использует — складываются числа «1» (первый аргумент) и «1» (вернул постинкремент). Получается «два».
Где эта тонкая грань, когда постинкремент в одном случае возвращает в сложение 2, а в другом 1?
Действительно хочется понять.

Евгений Степанищев (bolknote.ru) 2015

Комментарий для Dmitry:

В том и другом случае постинкремент работает одинаково, вычисляется просто в разом порядке.