LINUX.ORG.RU

i = ++i + ++i


0

0

Это я вно старый баян, но объясните, почему в C++ при i = 5 в результате будет 14? В Java результатом будт 13. А как в других языках?

★★

Ответ на: комментарий от OperaSoftware

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 нет.

Ian ★★
() автор топика

В Си результат выражения не определён согласно стандарту.

В других языках обычно поведение строго соответствует Сишному порядку действий.

Правая часть вычисляется слева на право, ++var инкрементирует переменную и возвращает увеличенное значение.

Левая часть получает вычисленное значение независимо от своего исходного значения.

...

Если в GCC забить на совместимость и скомпилировать такое выражение, то, логика результата будет такой: инкрементируем i дважды, складываем i+i, результат присваиваем i.

...

На самом деле ещё интереснее смотреть на i = i++ + i++.

KRoN73 ★★★★★
()

а поняла. только логика у компилятора тогда странная.

и вообще у меня голова заболела от этого примера. разве можно так писать? ведь непонятно же

OperaSoftware
()
Ответ на: комментарий от KRoN73

> инкрементируем i дважды, складываем i+i, результат присваиваем i.

а не 13 = 6 + 7 ? т.е. (5+1=6) + (6+1=7)

> На самом деле ещё интереснее смотреть на i = i++ + i++.


тут, имхо, и смотреть не на что. 10 будет сразу после сложения.

isden ★★★★★
()
Ответ на: комментарий от OperaSoftware

>разве можно так писать?

На любом (?) языке, кроме Си/Си++ - можно. На последнем поведение не определено в угоду оптимизатору.

>ведь непонятно же

Как раз всё очень просто, если знать, что обозначают ++var или var++ (а это обязан знать каждый программист на Си/Си++/Java/Perl/PHP/etc.) и в каком порядке выполняется вычисление операндов (слева на право) :)

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

Возможно я чего-то не понял, но

Увеличиваем i на единицу - первый операнд = 6, увеличиваем i еще на единицу - второй опреанд = 7. 6 + 7 = 13. Или в C++ изменение i при вычислении второго операнда отразиться и на уже вычисленном первом?

Ian ★★
() автор топика
Ответ на: комментарий от KRoN73

То есть не понятно почему это по стандарту undefined. Ведь есть разумный алгоритм вычисления такого выражения. Например в Java.

Ian ★★
() автор топика
Ответ на: комментарий от isden

>а не 13 = 6 + 7 ? т.е. (5+1=6) + (6+1=7)

По _логике_ элементарных операций - да. Но в стандарте Си оговорена сознательная неопределённость таких выражений. И мой пример относится к конкретному GCC коду:

int i;
scanf("%d", &i);
int b = ++i + ++i;
printf("%d", b);

код (-O3):

call scanf
movl -8(%ebp), %eax
addl $2, %eax
movl %eax, -8(%ebp)
addl %eax, %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf

Т.е. имеем предварительное увеличение на два переменной перед её сложением самой с собой.

В случае i++ + i++ будет такое же увеличение после вычиселения выражения.

>тут, имхо, и смотреть не на что. 10 будет сразу после сложения.

Плохо знаешь правила :D На всех языках, кроме Си/Си++ будет 11.

Берём i=5.
Инкрементируем, присваивая новое значение (6) переменной i. Значение до инкремента (5) используем как первый операнд.

Снова берём i (уже 6). Инкрементируем (7), значение до инкремента (6) используем как второй операнд.

5+6 = 11.

Результат пишем в i, игнорируя хранящееся там к этому времени число 7.

В случае же Си результат не определён.

KRoN73 ★★★★★
()
Ответ на: удаленный комментарий

> _по тому же адресу_

Спасибо, все понятно.

Ian ★★
() автор топика

очевидно, что 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 */

alex_custov ★★★★★
()
Ответ на: комментарий от Ian

>То есть не понятно почему это по стандарту 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 :)

Кстати, вот на такие грабли наступить намного проще, чем на сабж :)

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

Жесть... Благо на C/C++ редко приходится писать. =)

Ian ★★
() автор топика
Ответ на: комментарий от alex_custov

>очевидно, что gcc вычисляет аргументы так: вычисляет первый ++i,
вычисляет второй ++i

Нет, он не разделяет «первый» или «второй» i :) Он тупо увеличивает его на столько раз, сколько в выражении используется ++i, и итог использует в выражении.

А в твоём случае - просто сборка с -O0, поэтому он «по частям» выражение использует.

Вообще же: http://balancer.ru/tech/forum/2004/07/t27942--Skol~ko-budet-a-plus-plus-plus-...

...

Я там в лужу сел, не будучи знакомым с этим моментом стандарта и ожидая от Си «нормального» поведения. В итоге изначально задачу вообще как шутку принял и троллить начал :D Как же удивился потом, прогнав тест в GCC и VC :)

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

>Вообще же:

Для Ъ:

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

// ISO/IEC 14882:1998(E) -- C++ -- Expressions
// http://www.kuzbass.ru:8086/docs/isocpp/expr.html#expr.comma

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

> По _логике_ элементарных операций - да. Но в стандарте Си оговорена сознательная неопределённость таких выражений.

хм. а смысл?

> Плохо знаешь правила :D На всех языках, кроме Си/Си++ будет 11.

http://karma.nucleuscms.org/index.php/item/107 :)

isden ★★★★★
()
Ответ на: комментарий от KRoN73

>>Нет, он не разделяет «первый» или «второй» i :) Он тупо увеличивает его на столько раз, сколько в выражении используется ++i, и итог использует в выражении.

это всего лишь результат агрессивной оптимизации последовательности "++i; ++i;"

alex_custov ★★★★★
()
Ответ на: комментарий от KRoN73

>>А в твоём случае - просто сборка с -O0, поэтому он «по частям» выражение использует.

это то что он делает изначально, всё остальное - оптимизация исходного. Кстати, что за компилятор? Мой gcc-4.1.3 с -O >= 1 не складывает вообще, а сразу вычисляет и использует "14":

movl $14, 4(%esp)
movl $.LC0, (%esp)
call printf

alex_custov ★★★★★
()
Ответ на: комментарий от alex_custov

>это всего лишь результат агрессивной оптимизации последовательности "++i; ++i;"

В любом случае - инкрементирует переменную он до того, как начнёт вычислять выражение :)

>не складывает вообще, а сразу вычисляет и использует "14":

Потому я в своём примере scanf и использовал :)

KRoN73 ★★★★★
()

Некоторые компиляторы делают так: все преинкременты делают перед вычислением полного выражения, потом вычисляют выражение, потом вычисляют все постинкременты.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

>Некоторые компиляторы делают так

Вот GCC и VC именно так и делают :) Возможно, что icc тоже...

Ага, проверил. Приколько. Он никаких инкрементов делать не стал.

call scanf #6.2
# LOE ebx ebp esi edi
# Preds ..B1.6
movl 16(%esp), %eax #7.6
lea 2(%eax,%eax), %edx #7.12
movl %edx, 16(%esp) #7.6

Теперь я наглядно понимаю, почему icc генерирует более быстрый код :)

esp+16 - это где хранится переменная i.

А потом - опаньки, LEA EDX, [2+EAX+EAX]. Одной командой.

(кстати, непривычно видеть ассемблер с правым расположением приёмника, лет 16 с таким не сталкивался, со времён PDP)

KRoN73 ★★★★★
()
Ответ на: комментарий от KRoN73

Я бы на месте интел вставлял в такие места запуск генератора случайных чисел, чтобы люди использующие подобные выражения поскорей переходили на более подходящие для них языки :)

koTuk
()
Ответ на: комментарий от koTuk

Я не думаю, что кто-нибудь использует такие выражения. Это скорее задачка на знание работы компилятора.

Ian ★★
() автор топика

если считать, что ++i возвращает ссылку на i, то всё вполне объяснимо.

в отличие от i++, которая должна возвращать старое значение (именно значение) переменной.

xydo ★★
()
Ответ на: комментарий от ananas

volatile гарантирует только апдейт переменной в sequence point.

volatile не добавляет новых sequence points и ничего не делает с требованием о том что между двумя sequence points не должно быть двух записей в одно место.

dilmah ★★★★★
()
Ответ на: комментарий от anonymous

в данном случае все выражение находится между двумя секуэнс пойнтами, внутри их нет, volatile их не добавляет

dilmah ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.