Обсуждение:++i + ++i
Материал из Lurkmore
Анонимус, так откуда берется 14?
- Если первым делом производится 2 инкремента, то i = 7, а 7 + 7 = 14
- Капитан Очевидность объясняет для всех Слоупоков, как С++ получает число 14. Как уже было сказано, компилятор не рассчитывает выражение слева направо (как делает Жаба и Шарп), а сначала считает инкременты:
- Вот так-то!
- Что-то я не догнал краями. Мне казалось, что ++i возвращает значение после инкремента. То есть первый инкремент возвращает 6.
Ы. Вы мне лучше такой майндфак объясните:
int i = 5; i = (++i) + (++i) + (++i);
GCC. На выходе - 22. ШО ЗА ЁБАНЫЙ ЖЕ Ж НАХУЙ?
- а без скобков?
- один хуй. кто нибудь, попробуйте с другим компилятором. Алсо, при 4 повторах даёт 31(7+7+8+9, очевидно)
- Тогда сдаюсь.
В идеале, использование скобок должно изменять поведение, потому что обязывает вычислять.Я нуб.- ((++i) + (++i)) + (++i) => ( (5+1=6->7) + (6+1=7))+ (7+1=8)=(7+7)+8 - т.е. сводим к двухместным операциям и вычисляем конвеерно
- Тогда сдаюсь.
- один хуй. кто нибудь, попробуйте с другим компилятором. Алсо, при 4 повторах даёт 31(7+7+8+9, очевидно)
Анонимус, /r/ код на Лиспе, ну так. чисто поржать потому как (defvar *i* 5) (+ (incf *i*) (incf *i*)) уверенно дает 13.
правильный ответ - 13, в с++-компиляторах получается 14 из-за блядской оптимизации.
- Ты не шариш. В блядском c++ операция префикс-инкремента возвращает ссылку на обработанный объект, в отличие от пост-инкремента, которая и возвращает новый объект. (Это корректное поведение для, потому что при перегрузке инкрементов постфиксная версия юзает префиксную.) Поэтому и 14, соответственно 13 будет там, где префикс-инкремент возвращает новый объект.
- Давай по шагам для слоупоков. Первый инкремент увеличивает i до шести и возвращает ссылку на i. Второй инкремент увеличивает i до семи и возвращает опять-таки ссылку. Оператор сложения берёт две ссылки и складывает i c i, то есть 7 с 7. Поэтому 14. Так?
- Ну, если без доябывания до возврата ссылок во встроеных типах, то да. СутьЪ: ты изменяешь один объект, а не производишь новый. Например, (глядя на примеры в статье) java и c# при работе с элементарными типами возвращают новые объекты вне зависимости от ориентации ++, и на выходе есь 13. Впрочем, за такой код лопатой по рукам, да.
- Насилу вкурил. Может добавить в статью, что прикол в возвращении ссылки, а не значения в C++?
- Ты, в честь первого арпеля, смешься что-ли? Картинка сверху повествует обо всем этом даже лучше.
- По картинке не очевидно, что инкремент возвращает ссылку, а не значение.
- Капитан Очевидность Префиксный аргумент возвращает ссылку, но говорить об этом можно только для не-встроенных типов. Встроеным по барабану, посчитайте i++ + i++ для int и struct myInt{int x;} - получите разницу. Так-то!
- По картинке не очевидно, что инкремент возвращает ссылку, а не значение.
- Ты, в честь первого арпеля, смешься что-ли? Картинка сверху повествует обо всем этом даже лучше.
- Насилу вкурил. Может добавить в статью, что прикол в возвращении ссылки, а не значения в C++?
- Ну, если без доябывания до возврата ссылок во встроеных типах, то да. СутьЪ: ты изменяешь один объект, а не производишь новый. Например, (глядя на примеры в статье) java и c# при работе с элементарными типами возвращают новые объекты вне зависимости от ориентации ++, и на выходе есь 13. Впрочем, за такой код лопатой по рукам, да.
- Давай по шагам для слоупоков. Первый инкремент увеличивает i до шести и возвращает ссылку на i. Второй инкремент увеличивает i до семи и возвращает опять-таки ссылку. Оператор сложения берёт две ссылки и складывает i c i, то есть 7 с 7. Поэтому 14. Так?
>>int i = 5; i = (++i) + (++i) + (++i);
>>GCC. На выходе — 22. ШО ЗА ЁБАНЫЙ ЖЕ Ж НАХУЙ?
Я не считаю себя программистом, но мысли есть…
Итак, на входе gcc 4.3.3
int i=5; i = (++i) + (++i);
На выходе i=14
С точки зрения C++ это совершенно логичный ответ.
Левый инкремент по-идее имеет наивысший приоритет среди всех операций (наряду с декрементом).
Соответственно, по-идее это работает так:
1) ++i: i=6
2) ++i: i=7
3) i + i: i=14
Пример 2
int i=5; i = (++i) + (++i) + (++i);
По-идее это должно работать так
1) ++i: i=6
2) ++i: i=7
3) ++i: i=8
4) 8+8+8: i=24
Но выходит действительно 22. Вопрос в том, почему
- Потому что ((++i) + (++i)) + (++i) => ( (5+1=6->7) + (6+1=7))+ (7+1=8)=(7+7)+8 - т.е. сводим к двухместным операциям add и вычисляем конвеерно
- (в пределах операции сначала вычисляються побочные эффекты)
Пример 3
int i=5; i = (++i) + ((++i) + (++i)); (вторую часть мы взяли в скобки, изменив тем самым порядок сложений)скобки не меняют порядок применения побочных эффектов, просто теперь выражение в скобках считается отдельным выражением.
На выходе, внимание, 24!
Вывод — что-то явно не так с порядком выполнения операций.
- А здесь уже есть бинарная операция,но сначала вычисляются побочные эффекты, i'=6 (i'=7 i'=8) => 8 + 8 + 8=24
Пример 4
int i=5; i = ++i*2 + ++i;
Это должно работать так:
1) i++: i=6
2) i++: i=7
3) i=i*2+i: i=7*2+7=21
Как бы не так! На выходе 19!
- ничем ни отличается от примера 1 (++i*2 + ++i)=(6*2 + 7)=19
Моё предположение. gcc весьма странным образом вычисляет значение выражения. К примеру, возможно, выражение разбивается на части в соответствии с уровнями приоритетов операций и вычисляется по частям. При этом не учитывается тот факт, что в самом процессе вычисления выражения могут поменяться значения входящих в него переменных. Если принять это предположение, всё становится на места:
- Шо за хуйня? И не лень столько хуиты писать?
- Алгоритм
- 1.Расставляем скобки в соответствии с приоритетом операций / * + - и без всяких там блять инкрементов
- 2.Берем первые два операнда
- 3.Применяем побочный эффект первого операнда.Если операнд сложный - вычисляем выражение в скобках этим-же алгоритмом.
- 3а.Тоже для второго операнда
- 4.вычисляем двуместную операцию. результат => AX
- 5.Находим следующий операнд(если есть , иначе - выход)
- 6 применяем его побочные эффекты
- 7.operation(AX,след.операнд)
- 8.goto п.6
- проще говоря если представить списком
- 1.разбиваем его по приоритетам
- 2.потом на двуместные операции 5+5+5+5=((((5)+5)+5)+5)
- 3.для каждой двуместной сначала применяем побочные эффекты(с углублением в список) - потом вычисляем результат
- 4. ...
- 5. PROFIT!
- на Лиспе без учета типа операции это примерно (defun calc(l &optional Z)((if (NULL (car l)) Z (progn (side-effect (car l)) (сalc (cdr l) (operation Z (car l))))
- Не знаете ассемблерную команду [add|sub|mul|div] ax,op? Не знаете что такое рекурсия? Не считаете себя программистом, но хотите принести пользу человечеству?
- Вам поможет БИОРЕАКТОР! Спрашивайте в аптеках города...
Пример 2
int i=5; i = (++i) + (++i) + (++i);
1) ++i: i=6
2) i + ++i:
2.1) ++i: i=7
2.2) i + i = 14 (в этот момент мы потеряли i как одно из слагаемых-переменных, и получили вместо этого сумму как константу, которая уже не поменяется при следующем инкременте i), i=7
3) 14 + ++i:
3.1) ++i: i=8
3.2) i=14 + i: i=14+8=22
Пример 3 отличается от 2 дополнительными скобками
int i=5; i = (++i) + ((++i) + (++i));
1) ++i: i=6 (это то ++i, которое самое первое)
2) ((++i) + (++i))
2.1) ++i: i=7
2.2) ++i: i=8
2.3) i+i: 16 (тут мы получаем уже другую константу), i=8
3) i=i+16: i=8+16=24
Пример 4
int i=5; i = ++i*2 + ++i
1) ++i: i=6
2) i*2: 6*2=12, i=6 (обратите внимание, мы уже получили константу вместо переменной как в оригинальном примере. Дальнейшие манипуляции с i никакого влияния на нее не окажут)
3) ++i: i=7
4) i=12+i: i=12+7=19. Сходится
PS: Гораздо более интересно выглядят манипуляции с ПРАВЫМ инкрементом (он должен иметь НАИМЕНЬШИЙ приоритет с т.з. C++) Интересно то, что результаты по нему расходятся для gcc и для perl.
- Блять, а кто сказал что это с++, gcc умеет компилить исходники на разных языках - теже результаты вы получите и для Си.
Содержание |
[править] правильный ответ
пипец вы ламерье. для начала усвоим что ++j и j++ это разные вещи.
- a=++j - означает 1. j=j+1 2. a=j (который кагбе уже плюсанулся в первой операции)
- a=j++ - означает 1. a=j 2. j=j+1 (таким образом инкременция происходит уже после присвоения a=)
- отсюда. a = ++j + ++j - означает
- j=5; j=(++j[тут уже 6]) + ++j[тут уже 7]);
- таким образом j=5; j=(6 + 7]);
- верный ответ j= 13.
- учите матчасть, школие. это православный каноничный ответ. учите про приоритет операций (унарный предварительный инкремент ++j имеет высший приоритет исполнения)
Гы! Вот я ровно так думал, а потом ВНЕЗАПНО оказалось, что ++j возвращает не значение, а ссылку. В итоге — таки 14.
- Ты не совсем прав. Для встроеных типов нет конкретных правил возвращения, их действия определяет компилятор. То есть, может там ссылка и не возвращается вовсе, а весь код с этой переменной анализируется несколько раз. Но поведение должно быть такое, да. Кстати, на следующем примере действительно можно выловить ссылку:
#include <iostream>
int main()
{
int i = 5;
int& ri = ++i;
++i;
std::cout << ri; // здесь будет 7, пыщь-пыщь!!1
}
- Что там компилятор на сама деле делает — тайна великая для меня есть. Тем более, его поведение неопределено. Возвращение ссылки в случае двух слагаемых просто мое объяснение происходящего, вроде не противоречащее наблюдаемым фактам.
Ссылка ссылкой, но ++i + ++i + ++i даёт 22(7+7+8), а ++i + ++i + ++i + ++i - 31(7+7+8+9). Так что, самый настоящий андефайнд бихейвиор.
- А кто-то с этим спорит? В стандарте нопейсано же. Об самом операторе речь.
- Всё решает оптимизатор. Когда он решит из ссылки достать значение и запихать его в стек или в регистр - это его дело. По идее + имеет приоритет ниже, чем ++, поэтому должны вычислиться два ++, затем только их ЗНАЧЕНИЯ могут кому-то понадобиться, а поскольку их надо ещё и достать по ссылке, которая между прочим одинакова для обоих ++, вот тут то и получается интересно, особенно если оптимизатор решит это всё оптимизировать. Интуиция говорит, что результат ++i - это не ссылка, а i+1. Логика подсказывает, что при сложении чисел различной чётности получается полюбому нечётное число. Если бы было написано так: add(&(++i), &(++i)), то вопросов было бы меньше.
[править] настоящий правильный ответ
Предыдущий ответ нисколько не каноничный, он не лучше всех предыдущих. Дело здесь не в приоритете, конечно, а в валидности программы.
Стандарт языка С++ ISO/IEC 14882 от 1998г. вводил понятие sequence point [1.9:7,11,16], между которыми (скалярная) переменная может быть изменена только один раз [5:4], и там как раз приведены примеры, что i=++i + 1; // the behavior is unspecified. Выражение ++i никоим образом sequence point не вводит (если только это не вызов функции "перегруженный operator++" для пользовательских типов, т.е. не в этом случае).
В редакции стандарта от 2003г. это unspecified behavior стало[1] undefined, т.е. компилятор теперь имеет полное право вписать в переменную значение 42, вставить в программу код, форматирующий веник или взрывающий ваш монитор.
С этого момента, собственно говоря, вопрос перестает быть техническим и становится "коммуникационным".
Так как выражение вида i = ++i + ++i; с точки зрения языка невалидно, нельзя однозначно судить о том, что же хотел сказать автор, написав это злополучное выражение. (А вы попробуйте подойти к прохожему на улице и сказать "абырвалг!". Что за этим последует, можно только гадать. Вполне возможно, что вам "отформатируют веник".)
- Искренне ваш, Капитан Не Такая Очевидность.
- ↑ оно и было. пример с undefined не соответствовал остальному тексту, и примеры нормативными не являются
[править] Частности
Правиильный ответ выше. В этом разделе хуита, ибо рассматривает конкретный результат конкретного компилятора.
[править] ОЛСО
С точки зрения функциональной прагмиды:
++i - функция, возвращающая i + 1 с сайд-эффектом в виде i = i + 1
++i + ++i есть функция сложения с аргументами - результатами функций ++ с аргументом i
аргументы не могут вычисляться параллельно, т.к. при сайд-эффекте будет блокировка i
т.е. аргументы вычисляются один за другим, но похуй в каком порядке
В этом случае результатом выполнения сложения будет 13. Ежели без слайд-эффекта, то 12.
- Большая просьба к автору, определиться сайд-эффект это или
сЛайд-эффекти объяснить: что это за ХРЕНЬ?!
[править] хуита
[править] Delphi++
Насилу вкурил что к чему. Благо, расово верный объектный паскаль избавлен от таких заебов. Понимабельности коду такая хуета ниразу не добавляет. Программеров, написавших такое в моем отделе, ждет анальная окупация навечно.
- Delphi вообще няшечка в подобных вопросах. Там нет кейворда volatile, переупорядочивания операций, strict aliasing rule, etc - вместо этого всё пусть и чуть субоптимально, но соответствует интуитивным ожиданиям. Но вот что-то он никуда не пролазит: ни на 64 битные платформы, ни в мобильные девайсы, а до недавнего времени не было даже нормальной поддержки уникода.
- А вообще, непоправимое случилось не когда появился такой синтаксис в языке Си, а когда скриптовые языки и Java с дуру унаследовали этот синтаксис.
- В ANSI PASCAL вместо++ есть логические правильные функции Inc() и Dec(). В виду сего их поведение полностью предсказуемо. В отличие например значения счетчика цикла. Он прямо заявлен как UB.
- Да просто во времена последней расово верной D7 все было ориентровано на win32, а после семерки проект скорее мертв чем жив, и многочисленные попытки его оживить всегда заканчивались фэйлом. Виновата в этом прежде всего постоянная смена владельца. Тем не менее традиционные оконные приложения на ней пишут до сих пор, хотя число стартапов стремится к нулю.
- Виноват в этом не дельфи, и даже не Билл Гейтс, а безвестный инженер которому стукнуло в голову добавить в ассемблер PDP11 инструкции ++ и ++
- Да просто во времена последней расово верной D7 все было ориентровано на win32, а после семерки проект скорее мертв чем жив, и многочисленные попытки его оживить всегда заканчивались фэйлом. Виновата в этом прежде всего постоянная смена владельца. Тем не менее традиционные оконные приложения на ней пишут до сих пор, хотя число стартапов стремится к нулю.
[править] кто тут умный
нам препод вынес мозг и буду весьма благодарен если объясните как такой майндфак получает от 22 до 43 int i=5; i+=i+=i++ + ++i;
[править] VS C++ реализация
Сначала разберемся с приоритетами операций (в порядке возрастания и только встречающиеся в примерах)
0. +=
1. +
2. *
3. Префиксный ++
Теперь будем разбирать сами выражения:
1. int i = 5; i = ++i + ++i;
в соответствии с предоставленными выше приоритетами это i|++|i|++|+ ('|' - просто разделитель)
Выше показано как ++ на ассемблере реализуется:
LEA EAX,DWORD PTR SS:[EBP-4]<br />
INC DWORD PTR DS:[EAX]<br />
Т.е. инкрементится значение по адресу i, откуда получается что после i|++|i|++ значение i = 7. Осталось только сложить и получим 14
2. int i = 5; i = (++i) + (++i) + (++i). Вообще-то на скобки можно положить, т.е. рассматривать просто i = ++i + ++i + ++i, но со скобками более читабельно :)
Получаем i|++|i|++|+|i|++|+
Вот откуда получается 7+7+8 = 22 - сначала значение i становится равным 7 (первые два ++), потом идет операция +, далее i становится равным 8 (третий ++), и наконец последняя сумма 14+8 = 22
Далее покороче :)
3.int i=5; i = (++i) + ((++i) + (++i));
i|++|i|++|i|++|+|+
Т.е после всех ++ в i=8 и далее два раза складываем, откуда и получается 24
4. int i=5; i = ++i*2 + ++i;
i|++|2|*|i|++|+
Итог 6*2 + 7 = 19
5.int i=5; i = ++i + ++i + ++i + ++i
i|++|i|++|+|i|++|+|i|++|+
Итог 7 + 7 + 8 + 9 = 31
6. int i = 0; i += i++ + ++i
Тут постфиксный ++ не влияет вообще ни на что в выражении, он выполняется после всех операций:
i|i|i|++(префиксный)|+|+=|++(постфиксный)
Т.е. 1+1=2 потом 1+=2 что равно трем и в конце ++, откуда ответ 4.
1. 1+1 = 2
2. i+=2 равно 3 (i=1)
3. i++ = 4
7. int i=5; i+=i+=i++ + ++i;
i|i|i|++(префиксный)|+|+=|+=|++(постфиксный)
1. 6+6=12
2. i+=12 равно 18 (i=6)
3. i+=18 равно 36 (i=18)
4. i++ = 37
[править] Python
Кто шарит посмотрите, нужны ли тут скобки: (НИЧЕГО+НИЧЕГО+i)+(НИЧЕГО+НИЧЕГО+i) Если в Питоне нет очень сильного пробельного колдунства, то должно же быть без скобок: НИЧЕГО+НИЧЕГО+i + НИЧЕГО+НИЧЕГО+i
http://ideone.com/8INWz Таки 13
[править] js
Вот блин: javascript:var i=5;alert(++i + ++i)
13
А мозги-то перловые -- в уме 14 получилось :)
[править] Примечания о питоне и руби
Не уверен насчет питона, а про Руби примечание точно писал какой-то дятел, не открывший для себя существование в природе унарных операторов. А коль он такой умный рубист, пуст скажет, где описан метод «+» класса НИЧЕГО. Единственное НИЧЕГО, наличествующее в этом обсуждении — это у автора примечания в голове, а остальное — 4 унарных плюса.
[править] gcc мудрит чего-то....
Префиксный инкремент/декремент является операцией наивысшего приоритета, и поэтому должен выполнятся раньше всех других операций. Это по логике и програмер, который с ней дружит, такого поведения и ожидает. Т.е.
i = ++i + ++i; => 14
i = ++i + ++i + ++i; => 24
i = ++i + ++i + ++i + ++i; => 36
...
ну вы понели
Количество и месторасположение скобок в выражении результат не меняет, ибо скобки в данном выражении (где все бинарные операции одного и того же приоритета) приоритет операций изменить не могут.
Опять же, это по логике.
Ну, а если gcc выдает что-то иное, значит он где-то и как-то от этой самой логики уклоняется. GNU == ЧСВ => true.