++i + ++i

int i = 5;
i = ++i + ++i; — типичный код-майндфак для программистов.
В данном примере происходит неоднократное изменение переменной в пределах одной точки следования, такая ситуация описывается в стандартах C и С++ как UB. Иными словами, даже попытки ответить на этот вопрос иначе как «UB» демонстрируют недостаточную квалификацию отвечающего. Другое дело, что после «UB» можно указать некоторые подробности, и мы этим займёмся, поскольку не утоленное вовремя любопытство приводит к драмам в обсуждении.
Конкретно неопределённость в этой, как некоторым кажется, кристально ясной конструкции в данном случае заключается в том, что, согласно стандартам С и С++, побочные эффекты (то есть инкремент в данном случае) могут быть применены в "любой" удобный для компилятора момент между двумя точками следования . Конструкцию i = ++i + ++i; компилятор вправе понять и как
tmp=i; tmp++; i = tmp; tmp++; i += tmp;
и как
tmp=i; tmp++; tmp++; i = tmp + tmp;
и какими-нибудь другими способами. Нужна такая свобода для проведения низкоуровневых оптимизаций в обычных случаях типа a = ++b + ++c;, дабы между делом сэкономить пару тактов на халяву.
Хотя, оптимизатор тут вообще не при чём. Дело в том, что наше интуитивное понимание работы этого кода основывается на том, что прединкремент возвращает "значение", получившееся после прибавления единицы. На самом же деле любой нормальный прединкремент возвращает не получившееся значение, а "ссылку" на эту же переменную. Поэтому мы складываем не числа, а две одинаковые ссылки, то есть переменную i саму с собой! Иными словами происходит буквально следующее:
Левый ++i прибавляет единицу к i и возвращает ссылку на неё. I = 6.
Правый ++i прибавляет ещё одну единицу к i и также возвращает ссылку на неё. I = 7.
Оператор сложения разыменовывает ссылки, получая i = i + i. Так как после второго шага I = 7, то извлекается именно это число, давая выражение i = 7 + 7, откуда и получается 14.
Почему нельзя, мы уже видели (даже наиболее «альтернативно одаренные» не возьмутся оспаривать, что это ведет ко глюку). Однако всякий пытливый ум, несомненно, изучит этот глюк в поисках того, как именно может сглючить данный код, в какую сторону стандарты языка допускают маневры компилятора, а в какую — таки нет.
Варианты «правильного» ответа колеблются между 13 и 14, хотя на LISPе (пруф) анонимусы вроде как получили 12, 13 , а на Питоне вообще 10 (поскольку там вообще нет оператора инкремента ++). Результат зависит от последовательности операций. Там, где результат 13, сначала вычисляется первый инкремент, на место первого операнда идёт 6, вычисляется второй инкремент, туда идет 7, и результаты складываются: 6+7=13. 14 можно получить, вычислив оба инкремента, а потом уже сумму. Таким образом, этот трюк хорошо использовать для ошеломления быдлокодеров, уверенных в непогрешимости своего уютненького язычка.
Вообще, по определению предынкремент выполняется до вычисления выражения. Если считать ++i + ++i одним выражением, то мы должны выполнить два инкремента, потом вычислить выражение и получить 14. Если считать каждый ++i выражением, а сумму — выражением, принимающим результаты других выражений как аргументы, то, с точки зрения логики и математики, ничего таки не изменится, а вот компилятор, вычислив одно слагаемое (имеет право — самостоятельное выражение ведь!), упустит тот момент, что при вычислении второго такого же самостийного слагаемого первое тоже, сцуко, подло изменилось. Указать синтаксисом, где начинается и заканчивается выражение, невозможно — скобки задают приоритет внутри выражения. «Выражение» — другая инстанция. Например, в А=(В=С+14) есть выражение С+14, результат которого присваивается В, причем присваивание — тоже является выражением, и его результат присваивается А. И никакие скобки не могут побудить ЭТО стать одним выражением. Разъединить выражение, напротив, можно: (a=++i) + (b=++i), что напрямую задает вычисление a, вычисление b и только потом вычисление суммы a+b. Это, теоретически, должно детерминировать значение a+b как 13 (если компилятор настолько строго выдерживает формальные определения языка), но зато содержит UB относительно значений a и b, поскольку оптимизировать порядок вычисления a и b компилятору никто не запрещает (см. точки следования). Очевидно, что значение (a=++i) / (b=++i) по этой причине вообще не детерминировано.
text=KoloDen
Привет, я общительный пацан, люблю поболтать, особенно с классными девченками. Но, чтобы поговорить со мной, ответьте на простую задачку анти-спам бота. Вот она:
int i = 5;
i = ++i + ++i;
Вопрос: Чему равно i?
Stefmania 14
KoloDen Гы. Признайся, ты не девченка, а 40-летний одмин, да?