dmitry@dmitry-desktop:~/Desktop$ cat test.java
public class test
{
public static void main(String[] args)
{
int i = 5;
i = ++i + ++i;
System.out.println(i);
}
}
dmitry@dmitry-desktop:~/Desktop$ java test
13
Вот почему 13 мне понятно, а почему 14 нет.
В Си результат выражения не определён согласно стандарту.
В других языках обычно поведение строго соответствует Сишному порядку действий.
Правая часть вычисляется слева на право, ++var инкрементирует переменную и возвращает увеличенное значение.
Левая часть получает вычисленное значение независимо от своего исходного значения.
...
Если в GCC забить на совместимость и скомпилировать такое выражение, то, логика результата будет такой: инкрементируем i дважды, складываем i+i, результат присваиваем i.
...
На самом деле ещё интереснее смотреть на i = i++ + i++.
На любом (?) языке, кроме Си/Си++ - можно. На последнем поведение не определено в угоду оптимизатору.
>ведь непонятно же
Как раз всё очень просто, если знать, что обозначают ++var или var++ (а это обязан знать каждый программист на Си/Си++/Java/Perl/PHP/etc.) и в каком порядке выполняется вычисление операндов (слева на право) :)
Увеличиваем i на единицу - первый операнд = 6, увеличиваем i еще на единицу - второй опреанд = 7. 6 + 7 = 13. Или в C++ изменение i при вычислении второго операнда отразиться и на уже вычисленном первом?
По _логике_ элементарных операций - да. Но в стандарте Си оговорена сознательная неопределённость таких выражений. И мой пример относится к конкретному GCC коду:
int i;
scanf("%d", &i);
int b = ++i + ++i;
printf("%d", b);
очевидно, что gcc вычисляет аргументы так: вычисляет первый ++i,
вычисляет второй ++i _по тому же адресу_, и получает в итоге 7+7.
gcc -S всё показывает:
1)
int i = 5;
i = ++i + ++i;
даёт
movl $5, -8(%ebp)
addl $1, -8(%ebp)
addl $1, -8(%ebp)
/* i == 7 */
2)
int i = 5;
int j = 6;
i = ++i + ++j;
даёт
movl $5, -12(%ebp)
movl $6, -8(%ebp)
addl $1, -12(%ebp)
addl $1, -8(%ebp)
/* i == 6, j == 7 */
>То есть не понятно почему это по стандарту undefined.
В случае жёсткой оптимизации, при использовании множественных i++, инкрементация переменной происходит не по мере её использования, а после вычисления всего выражения.
int a[] = { 1, 2, 3 };
int b[] = { 10, 20, 30, 40 };
int i = 0;
printf("%d\n", a[i++] + b[i++]);
printf("%d\n", a[i++] + b[i++]);
результат (GCC):
11
33
В то время, как «по логике» - 21 и 43 :)
Кстати, вот на такие грабли наступить намного проще, чем на сабж :)
>очевидно, что gcc вычисляет аргументы так: вычисляет первый ++i,
вычисляет второй ++i
Нет, он не разделяет «первый» или «второй» i :) Он тупо увеличивает его на столько раз, сколько в выражении используется ++i, и итог использует в выражении.
А в твоём случае - просто сборка с -O0, поэтому он «по частям» выражение использует.
Я там в лужу сел, не будучи знакомым с этим моментом стандарта и ожидая от Си «нормального» поведения. В итоге изначально задачу вообще как шутку принял и троллить начал :D Как же удивился потом, прогнав тест в GCC и VC :)
Between the previous and next sequence point a scalar object shall have its stored value modified at most once by the evaluation of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored. The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full expression; otherwise the behavior is undefined. [Example]:
i = v[i++]; // the behavior is unspecified
i = 7, i++, i++; // i becomes 9
i = ++i + 1; // the behavior is unspecified
i = i + 1; // the value of i is incremented
>>Нет, он не разделяет «первый» или «второй» i :) Он тупо увеличивает его на столько раз, сколько в выражении используется ++i, и итог использует в выражении.
это всего лишь результат агрессивной оптимизации последовательности "++i; ++i;"
>>А в твоём случае - просто сборка с -O0, поэтому он «по частям» выражение использует.
это то что он делает изначально, всё остальное - оптимизация исходного. Кстати, что за компилятор? Мой gcc-4.1.3 с -O >= 1 не складывает вообще, а сразу вычисляет и использует "14":
Некоторые компиляторы делают так: все преинкременты делают перед вычислением полного выражения, потом вычисляют выражение, потом вычисляют все постинкременты.
Я бы на месте интел вставлял в такие места запуск генератора случайных чисел, чтобы люди использующие подобные выражения поскорей переходили на более подходящие для них языки :)
volatile гарантирует только апдейт переменной в sequence point.
volatile не добавляет новых sequence points и ничего не делает с требованием о том что между двумя sequence points не должно быть двух записей в одно место.