Для я = 0, почему (i + = я ++) равно 0?

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

static void Main(string[] args)
{
    int i = 0;
    i += i++;
    Console.WriteLine(i);
    Console.ReadLine();
}

Результат i равен 0. Я ожидал 2 (как это делали некоторые мои коллеги). Вероятно, компилятор создает некоторую структуру, которая приводит к нулю i.

Причина, по которой я ожидал 2, заключается в том, что в моей мысли мы сначала оцениваем инструкцию правой руки, увеличивая я с 1. Чем она добавляется к i. Поскольку я уже 1, он добавляет 1 к 1. Итак, 1 + 1 = 2. Очевидно, это не то, что происходит.

Можете ли вы объяснить, что делает компилятор или что происходит во время выполнения? Почему результат равен нулю?

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

Ответ 1

Это:

int i = 0;
i += i++

Можно видеть, как вы это делаете (следующее грубое упрощение):

int i = 0;
i = i + i; // i=0 because the ++ is a postfix operator and hasn't been executed
i + 1; // Note that you are discarding the calculation result

Что на самом деле происходит больше, чем это - взгляните на MSDN, 7.5.9 Операторы приращения и уменьшения постфикса:

Обработка времени выполнения операции увеличения или уменьшения постфиксации формы x ++ или x-- состоит из следующих шагов:

  • Если x классифицируется как переменная:

    • x вычисляется для получения переменной.
    • Сохраняется значение x.
    • Выбранный оператор вызывается с сохраненным значением x в качестве аргумента.
    • Значение, возвращаемое оператором, сохраняется в местоположении, заданном оценкой x.
    • Сохраненное значение x становится результатом операции.

Обратите внимание, что из-за порядка приоритета постфикс ++ встречается до +=, но результат не используется (как предыдущее значение i).


Более тщательное разложение i += i++ на части, из которых оно сделано, требует знать, что оба += и ++ не являются атомарными (то есть ни одна из них не является одной операцией), даже если они выглядят как они есть. Способ их реализации включает временные переменные, копии i перед выполнением операций - по одному для каждой операции. (Я буду использовать имена iAdd и iAssign для временных переменных, используемых для ++ и += соответственно).

Итак, более близкое приближение к тому, что происходит, будет:

int i = 0;
int iAdd = i; // Copy of the current value of i, for ++
int iAssign = i; // Copy of the current value of i, for +=

i = i + 1; // i++ - Happens before += due to order of precedence
i = iAdd + iAssign;

Ответ 2

Разборка текущего кода:

int i = 0;
  xor         edx, edx
  mov         dword ptr i, edx         // set i = 0
i += i++;
  mov         eax, dword ptr i         // set eax = i (=0)
  mov         dword ptr tempVar1, eax  // set tempVar1 = eax (=0)
  mov         eax, dword ptr i         // set eax = 0 ( again... why??? =\ )
  mov         dword ptr tempVar2, eax  // set tempVar2 = eax (=0)
  inc         dword ptr i              // set i = i+1 (=1)
  mov         eax, dword ptr tempVar1  // set eax = tempVar1 (=0)
  add         eax, dword ptr tempVar2  // set eax = eax+tempVar2 (=0)
  mov         dword ptr i, eax         // set i = eax (=0)

Эквивалентный код

Он компилируется с тем же кодом, что и следующий код:

int i, tempVar1, tempVar2;
i = 0;
tempVar1 = i; // created due to postfix ++ operator
tempVar2 = i; // created due to += operator
++i;
i = tempVar1 + tempVar2;

Разборка второго кода (просто чтобы доказать, что они одинаковы)

int i, tempVar1, tempVar2;
i = 0;
    xor         edx, edx
    mov         dword ptr i, edx
tempVar1 = i; // created due to postfix ++ operator
    mov         eax, dword ptr i
    mov         dword ptr tempVar1, eax
tempVar2 = i; // created due to += operator
    mov         eax, dword ptr i
    mov         dword ptr tempVar2, eax
++i;
    inc         dword ptr i
i = tempVar1 + tempVar2;
    mov         eax, dword ptr tempVar1
    add         eax, dword ptr tempVar2
    mov         dword ptr i, eax

Открытие окна разборки

Большинство людей не знают или даже не помнят, что они могут видеть окончательный код сборки в памяти, используя окно Visual Studio Разборка. Он показывает исполняемый машинный код, это не CIL.

Используйте это при отладке:

Debug (menu) -> Windows (submenu) -> Disassembly

Итак, что происходит с postfix ++?

Постфикc++ сообщает, что мы хотели бы увеличить значение операнда после оценки... что все знают... что немного сбивает с толку значение "после оценки".

Итак, что означает "после оценки":

  • должны использоваться другие способы использования операнда в одной строке кода:
    • a = i++ + i на второй я влияет инкремент
    • Func(i++, i) второй я затронут
  • другие применения в одной строке относятся к оператору короткого замыкания, например || и &&:
    • (false && i++ != i) || i == 0 третий я не зависит от я ++, потому что он не оценивается

Итак, в чем смысл: i += i++;?

Это то же самое, что i = i + i++;

Порядок оценки:

  • Сохранить я + я (то есть 0 + 0)
  • Приращение я (i становится 1)
  • Назначить значение шага 1 я (i становится 0)

Не то, чтобы приращение отбрасывалось.

В чем смысл: i = i++ + i;?

Это не то же самое, что в предыдущем примере. Третий i зависит от приращения.

Порядок оценки:

  • Сохранить я (то есть 0)
  • Приращение я (i становится 1)
  • Сохранить значение для шага 1 + я (то есть 0 + 1)
  • Назначить значение шага 3 я (i становится 1)

Ответ 3

int i = 0;
i += i++;

оценивается следующим образом:

Stack<int> stack = new Stack<int>();
int i;

// int i = 0;
stack.Push(0);                   // push 0
i = stack.Pop();                 // pop 0 --> i == 0

// i += i++;
stack.Push(i);                   // push 0
stack.Push(i);                   // push 0
stack.Push(i);                   // push 0
stack.Push(1);                   // push 1
i = stack.Pop() + stack.Pop();   // pop 0 and 1 --> i == 1
i = stack.Pop() + stack.Pop();   // pop 0 and 0 --> i == 0

то есть. i изменяется дважды: один раз выражением i++ и один раз с помощью оператора +=.

Но операнды оператора +=

  • значение i перед оценкой i++ (левая сторона +=) и
  • значение i перед оценкой i++ (правая часть +=).

Ответ 4

Во-первых, i++ возвращает 0. Затем i увеличивается на 1. Наконец i устанавливается на начальное значение i, которое равно 0 плюс возвращаемое значение i++, которое тоже равно нулю. 0 + 0 = 0.

Ответ 5

Это просто слева направо, снизу-вверх абстрактное синтаксическое дерево. Понятно, что дерево выражений идет сверху вниз, но оценка разворачивается, когда рекурсия всплывает под деревом снизу.

// source code
i += i++;

// abstract syntax tree

     +=
    /  \
   i    ++ (post)
         \
         i

Оценка начинается с рассмотрения корня node +=. Это основная составляющая выражения. Левый операнд += должен быть оценен, чтобы определить место, где мы храним эту переменную, и получить предыдущее значение, которое равно нулю. Затем следует оценить правую сторону.

Правая сторона - это пост-инкрементирующий оператор ++. Он имеет один операнд i, который оценивается как источник значения, и как место, где должно храниться значение. Оператор оценивает i, находя 0 и, следовательно, сохраняет a 1 в этом месте. Он возвращает предыдущее значение 0 в соответствии с его семантикой возврата предыдущего значения.

Теперь управление возвращается к оператору +=. Теперь у него есть вся информация для завершения операции. Он знает место хранения результата (место хранения i), а также предыдущее значение, и оно имеет значение, добавленное к предыдущему значению, а именно 0. Итак, i заканчивается нулем.

Подобно Java, С# дезинформировал очень асинхронный аспект языка C, установив порядок оценки. Слева направо, снизу вверх: самый очевидный порядок, который, скорее всего, ожидается от кодеров.

Ответ 6

Потому что i++ сначала возвращает значение, а затем увеличивает его. Но после того, как я установлено в 1, вы установите его на 0.

Ответ 7

Метод post-increment выглядит примерно так:

int ++(ref int i)
{
    int c = i;
    i = i + 1;
    return c;
}

Таким образом, в основном, когда вы вызываете i++, i является приращением, но исходное значение возвращается в вашем случае, когда возвращается 0.

Ответ 8

i ++ означает: возвращает значение я THEN, увеличивая его.

i + = я ++ означает: Возьмите текущее значение i. Добавьте результат я ++.

Теперь добавим в я = 0 в качестве начального условия. i + = я ++ теперь оценивается следующим образом:

  • Какое текущее значение i? Это 0. Сохраните его, чтобы мы могли добавить к нему результат я ++.
  • Оценить я ++ (оценивается как 0, потому что текущее значение i)
  • Загрузите сохраненное значение и добавьте к нему результат шага 2. (добавить от 0 до 0)

Примечание. В конце шага 2 значение я равно 1. Однако на шаге 3 вы отбрасываете его, загружая значение я до того, как он будет увеличен.

В отличие от я ++, ++ я возвращает добавочное значение.

Следовательно, я + = ++ я даст вам 1.

Ответ 9

Простой ответ

int i = 0;
i += i++;
// Translates to:
i = i + 0; // because post increment returns the current value 0 of i
// Before the above operation is set, i will be incremented to 1
// Now i gets set after the increment,
// so the original returned value of i will be taken.
i = 0;

Ответ 10

Оператор приращения post fix, ++, дает переменной значение в выражении, а затем добавляет значение, которое вы назначили, возвращаемое значение 0 (0) в значение i, которое перезаписывает инкрементированное значение (1), поэтому вы становятся нулевыми. Вы можете больше узнать об операторе приращения в ++ Operator (MSDN).

Ответ 11

i += i++; будет равен нулю, так как после этого он ++.

i += ++i; сделает это до

Ответ 12

++ postfix оценивает i, прежде чем увеличивать его, а += оценивает только i один раз.

Следовательно, 0 + 0 = 0, поскольку i оценивается и используется до его приращения, поскольку используется формат postfix ++. Чтобы сначала получить значение i, используйте форму префикса (++i).

(Кроме того, просто примечание: вы должны получить только 1, как 0 + (0 + 1) = 1)

Ссылки: http://msdn.microsoft.com/en-us/library/sa7629ew.aspx (+ =)
          http://msdn.microsoft.com/en-us/library/36x43w8w.aspx (++)

Ответ 13

Что делает С#, и "почему" путаницы

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

Косайдер:

    static int SetSum(ref int a, int b) { return a += b; }

    static int Inc(ref int a) { return a++; }

Я ожидал, что i += i++ будет таким же, как SetSum(ref i, Inc(ref i)). Значение я после этого оператора 1:

int i = 0;
SetSum(ref i, Inc(ref i));
Console.WriteLine(i); // i is 1

Но потом я пришел к другому выводу... i += i++ на самом деле то же самое, что и i = i + i++... поэтому я создал еще один подобный пример, используя следующие функции:

    static int Sum(int a, int b) { return a + b; }

    static int Set(ref int a, int b) { return a = b; }

После вызова этого Set(ref i, Sum(i, Inc(ref i))) значение я будет 0:

int i = 0;
Set(ref i, Sum(i, Inc(ref i)));
Console.WriteLine(i); // i is 0

Это не только объясняет, что делает С#, но и почему многие люди путались с ним... включая меня.

Ответ 14

Хорошая мнемоника. Я всегда помню об этом:

Если ++ стоит после выражения, оно возвращает значение до. Таким образом, следующий код

int a = 1;
int b = a++;

равно 1, так как a был 1 до, он увеличился на ++ стоящий после a. Люди называют эту запись post. Существует также нотация pre, где все в точности противоположно: если ++ стоит до, выражение возвращает значение, которое оно после операция:

int a = 1;
int b = ++a;

b здесь два.

Итак, для вашего кода это означает

int i = 0;
i += (i++);

i++ возвращает 0 (как описано выше), поэтому 0 + 0 = 0.

i += (++i); // Here 'i' would become two

Скотт Майерс описывает разницу между этими двумя обозначениями в "Эффективном программировании на C++". Внутри i++ (постфикс) запоминает значение i was и вызывает префикс-нотацию (++i) и возвращает старое значение i. Вот почему вы всегда должны использовать ++i в циклах for (хотя я думаю, что все современные компиляторы переводят i++ в ++i в циклы for).

Ответ 15

Единственный ответ на ваш правильный вопрос: Потому что это undefined.

Хорошо, прежде чем вы все сожжете меня.

Вы все ответили, почему i+=i++ нормально и логично привести к i=0.

У меня возникло соблазн проголосовать за каждый 1 из ваших ответов, но рейтинг репутации, который я подсчитал, будет слишком высоким.

Почему я так злюсь на тебя, люди? не из-за того, что объясняет ваши ответы..
Я имею в виду, что каждый ответ, который я прочитал, сделал замечательную попытку объяснить невозможное, я Аплодисменты!

Но каков результат? это интуитивный результат - это приемлемый результат?

Каждый из вас видел "голого царя" и каким-то образом принимал его как разумного короля.

Вы все НЕПРАВИЛЬНО!

i+=i++; результат 0 равен undefined.

ошибка в механизме оценки языка, если вы... или даже хуже! ошибка в дизайне.

Хотите доказательство? конечно, вы хотите!

int t=0; int i=0; t+=i++; //t=0; i=1

Теперь это... интуитивный результат! потому что мы сначала оценили t присваивали ему значение, и только после оценки и назначения у нас была операция post - рациональная, не так ли?

рационально, что: i=i++ и i=i дают тот же результат для i?

а t=i++ и t=i имеют разные результаты для i.

Операция post - это то, что должно произойти после оценки оператора.
Поэтому:

int i=0;
i+=i++;

Должен совпадать, если мы написали:

int i=0;
i = i + i ++;

и, следовательно, так же, как:

int i=0;
i= i + i;
i ++;

и, следовательно, так же, как:

int i=0;
i = i + i;
i = i + 1;

Любой результат, который не является 1, указывает на ошибку в компиляторе или ошибку в дизайне языка, если мы пойдем с рациональным мышлением, однако MSDN и многие другие источники говорят нам "эй - это undefined!"

Теперь, прежде чем продолжить, даже этот набор примеров, которые я дал, не поддерживается и не подтверждается кем-либо. Однако это то, что в соответствии с интуитивным и рациональным способом должно было быть результатом.

Кодер не должен знать, как записывается или переводится сборка!

Если он написан таким образом, который не будет уважать определения языка - это ошибка!

И чтобы закончить, я скопировал это из Википедии Операторы увеличения и уменьшения:
Так как оператор increment/decment модифицирует свой операнд, использование такого операнда более одного раза в одном выражении может привести к результатам undefined. Например, в выражениях, таких как x - ++ x, не ясно, в какой последовательности должны выполняться операторы вычитания и приращения. Ситуации, подобные этому, становятся еще хуже, когда оптимизаторы применяются компилятором, что может привести к тому, что порядок выполнения операций будет отличаться от того, что планировал программист.

И поэтому.

Правильный ответ: это НЕ ДОЛЖНО ИСПОЛЬЗОВАТЬСЯ! (так как это undefined!)

Да.. - Он имеет непредсказуемые результаты, даже если С# complier пытается каким-то образом его нормализовать.

Я не нашел никакой документации по С#, описывающей поведение, которое вы описали как нормальное или четко определенное поведение языка. То, что я нашел, - это полная противоположность!

[скопировано из документации MSDN для операторов приращения и сокращения Postfix: ++ и -]

Когда к аргументу функции применяется постфиксный оператор, значение аргумента не гарантируется увеличивается или уменьшается до того, как оно будет передано функции. Дополнительную информацию см. В разделе 1.9.17 в стандарте С++.

Обратите внимание на те слова не гарантируется...

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

Ответ 16

Оператор ++ после переменной делает его постфиксным приращением. Приращение происходит после всего остального в инструкции, добавления и назначения. Если вместо этого вы помещаете ++ перед переменной, это произойдет до того, как значение будет оценено, и дайте ожидаемый ответ.

Ответ 17

Шагами в вычислении являются:

  • int i=0//Инициализировано до 0
  • i+=i++//Уравнение
  • i=i+i++//после упрощения уравнения компилятором
  • i=0+i++//i замена значения
  • i=0+0//i ++ - это 0, как описано ниже
  • i=0//Окончательный результат я = 0

Здесь первоначально значение i равно 0. WKT, i++ не что иное: сначала используйте значение i, а затем увеличивайте значение i на 1. Итак он использует значение i, 0, вычисляя i++, а затем увеличивает его на 1. Таким образом, это приводит к значению 0.

Ответ 18

Есть два варианта:

Первый вариант: если компилятор читает инструкцию следующим образом,

i++;
i+=i;

то результат равен 2.

Для

else if
i+=0;
i++;

результат равен 1.

Ответ 19

Будьте очень осторожны: прочитайте C FAQ: что вы пытаетесь сделать (смешение присвоения и ++ того же переменная) не только неопределенна, но также и undefined (это означает, что компилятор может делать что-либо при оценке!, не только дает "разумные" результаты).

Прочтите, раздел 3. Весь раздел стоит прочитать! Особенно 3.9, что объясняет импликацию неуточненной. Раздел 3.3 дает вам краткое описание того, что вы можете и чего не можете сделать, с "i ++" и т.п.

В зависимости от внутренних компонентов компиляторов вы можете получить 0, или 2, или 1, или даже что-нибудь еще! И поскольку это undefined, это нормально для них.

Ответ 20

В надежде ответить на это с точки зрения C-программирования 101.

Мне кажется, что это происходит в следующем порядке:

  • i оценивается как 0, в результате получается i = 0 + 0 с операцией increment i++ "queued", но присваивание от 0 до i еще не произошло.
  • Приращение i++ происходит
  • Назначение i = 0 сверху происходит, эффективно перезаписывая все, что было бы сделано # 2 (post-increment).

Теперь, # 2, возможно, никогда не произойдет (возможно, нет?), потому что компилятор, вероятно, понимает, что это нецелесообразно, но это может быть зависимым от компилятора. В любом случае, другие, более осведомленные ответы показали, что результат верен и соответствует стандарту С#, но он не определил, что происходит здесь для C/С++.

Как и почему выходит за рамки моего опыта, но тот факт, что ранее оцененное правостороннее присваивание происходит после пошагового ввода, вероятно, является чем-то путающим здесь.

Кроме того, вы не ожидали, что результат будет равен 2 независимо, если вы не сделали ++i вместо i++, я считаю.

Ответ 21

Проще говоря,

i ++, добавит 1 к "i" после завершения оператора "+ =".

То, что вы хотите, это ++ i, так что он добавит 1 к "i" до того, как будет выполнен оператор "+ =".

Ответ 22

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

int i = 0;
i+ = i++;

Здесь результат я показывает 0 результат. Теперь рассмотрим ниже случаи:

Случай 1:

i = i++ + i; //Answer 1

раньше я думал, что код напоминает это, так что сначала посмотреть ответ 1, и действительно ответ я для этого один.

Случай 2:

i = i + i++; //Answer 0 this resembles the question code.

здесь оператор increment не входит в путь выполнения, в отличие от предыдущего случая, когда я ++ имеет возможность выполнить перед добавлением.

Надеюсь, это немного поможет. Благодаря

Ответ 23

i=0

i+=i

i=i+1

i=0;

Затем 1 добавляется к i.

i + = я ++

Итак, перед добавлением 1 в i, i взяло значение 0. Только если мы добавим 1 до, i получаем значение 0.

i+=++i

i=2

Ответ 24

Ответ i будет 1.

Посмотрим, как:

Изначально i=0;.

Тогда при вычислении i +=i++; по значению мы будем иметь что-то вроде 0 +=0++;, поэтому в соответствии с приоритетом оператора 0+=0 будет выполняться первым, а результат будет 0.

Тогда оператор приращения будет применяться как 0++, как 0+1, а значение i будет 1.