c - порядок - с++ приоритет




Почему эти конструкции используют предопределенное поведение до и после инкремента? (10)

#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d %d\n", w++, ++w, w); // shouldn't this print 0 2 2

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}

C имеет концепцию неопределенного поведения, то есть некоторые языковые конструкции синтаксически верны, но вы не можете предсказать поведение при запуске кода.

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

Итак, имея в виду, почему эти «проблемы»? Язык четко говорит о том, что определенные вещи приводят к неопределенному поведению . Нет проблем, нет «необходимости». Если неопределенное поведение изменяется, когда одна из задействованных переменных объявлена volatile , это ничего не доказывает или не изменяет. Он не определен ; вы не можете рассуждать о поведении.

Ваш наиболее интересный пример, тот, с

u = (u++);

представляет собой текстовый пример неопределенного поведения (см. запись Википедии о точках последовательности ).


Большинство ответов здесь цитируется из стандарта C, подчеркивая, что поведение этих конструкций не определено. Чтобы понять, почему поведение этих конструкций не определено , давайте сначала разобраться в этих терминах в свете стандарта C11:

Последовательность: (5.1.2.3)

При любых двух оценках A и B , если A секвенирован до B , то выполнение A должно предшествовать исполнению B

Unsequenced:

Если A не секвенируется до или после B , то A и B подвержены влиянию.

Оценки могут быть одной из двух вещей:

  • вычислений значений , которые выработают результат выражения; а также
  • побочные эффекты , которые являются модификациями объектов.

Точка последовательности:

Наличие точки последовательности между оценкой выражений A и B означает, что каждое вычисление значения и побочный эффект, связанные с A , секвенируются перед вычислением каждого значения и побочным эффектом, связанным с B

Теперь, придя к вопросу, для выражений типа

int i = 1;
i = i++;

стандарт говорит, что:

6.5 Выражения:

Если побочный эффект скалярного объекта не зависит от другого побочного эффекта для одного и того же скалярного объекта или вычисления значения с использованием значения одного и того же скалярного объекта, поведение не определено . [...]

Следовательно, вышеупомянутое выражение вызывает UB, потому что два побочных эффекта на одном i том же объекте i не зависят от друг друга. Это означает, что он не секвенирован, будет ли побочный эффект назначением на i до или после побочного эффекта на ++ .
В зависимости от того, будет ли выполняться до или после приращения, будут созданы разные результаты, и это один из случаев неопределенного поведения .

Позволяет переименовать i слева от присваивания be il и справа от присваивания (в выражении i++ ) - ir , тогда выражение будет выглядеть как

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Важным моментом для оператора Postfix ++ является то, что:

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

Это означает, что выражение il = ir++ можно оценить либо как

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

или же

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

что приводит к двум различным результатам 1 и 2 которые зависят от последовательности побочных эффектов путем присваивания и ++ и, следовательно, вызывает UB.


В стандарте C говорится, что переменная должна назначаться не более одного раза между двумя точками последовательности. Например, точка с запятой - это точка последовательности.
Поэтому каждое утверждение формы:

i = i++;
i = i++ + ++i;

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

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

while(*src++ = *dst++);

Вышеприведенное является общей практикой кодирования при копировании / анализе строк.


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

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

Второй фрагмент, i = i++ , немного легче понять. Кто-то явно пытается увеличить i и присвоить результат i. Но есть несколько способов сделать это в C. Самый простой способ добавить 1 к i и присвоить результат обратно i, тот же почти на любом языке программирования:

i = i + 1

C, конечно, имеет удобный ярлык:

i++

Это означает, что «добавьте 1 в i и присвойте результат обратно i». Поэтому, если мы построим мешанину из двух, написав

i = i++

что мы действительно говорим: «добавьте 1 в i и присвойте результат обратно i, и присвойте результат обратно i». Мы в замешательстве, поэтому меня это не беспокоит, если компилятор тоже запутался.

Реально, единственный раз, когда эти сумасшедшие выражения записываются, - это когда люди используют их как искусственные примеры того, как должен работать ++. И, конечно же, важно понимать, как работает ++. Но одно практическое правило для использования ++: «Если не очевидно, что означает выражение, использующее ++, не пишите».

Мы проводили бесчисленные часы на comp.lang.c, обсуждая такие выражения, и почему они не определены. Два моих более длинных ответа, которые пытаются объяснить, почему, архивируются в Интернете:


Причина в том, что в программе выполняется неопределенное поведение. Проблема заключается в порядке оценки, потому что нет никаких точек последовательности, требуемых согласно стандарту C ++ 98 (никакие операции не секвенированы до или после другого в соответствии с терминологией C ++ 11).

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

  • Итак, сначала GCC: Используя Nuwen MinGW 15 GCC 7.1, вы получите:

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2
    

    }

Как работает GCC? он оценивает подвыражения в порядке слева направо для правой стороны (RHS), затем присваивает значение левой стороне (LHS). Именно так ведут себя Java и C # и определяют их стандарты. (Да, эквивалентное программное обеспечение на Java и C # определило поведение). Он оценивает каждое вспомогательное выражение один за другим в Заявлении RHS в порядке слева направо; для каждого вспомогательного выражения: сначала выполняется оценка ++ c (pre-increment), затем значение c используется для операции, а затем приращение post c ++).

в соответствии с GCC C ++: Операторы

В GCC C ++ приоритет операторов контролирует порядок, в котором оцениваются отдельные операторы

эквивалентный код в определенном поведении C ++, как понимает GCC:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

Затем переходим к Visual Studio . Visual Studio 2015 вы получаете:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

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

Таким образом, эквивалент в определенном поведении C ++ как Visual C ++ понимает:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

как документация Visual Studio указывает на приоритет и порядок оценки :

Когда несколько операторов появляются вместе, они имеют равный приоритет и оцениваются в соответствии со своей ассоциативностью. Операторы в таблице описаны в разделах, начинающихся с операторов Postfix.


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

Это то, что я получаю на своей машине вместе с тем, что, как я думаю, происходит:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(Я ... предположим, что инструкция 0x00000014 была своего рода оптимизацией компилятора?)


Хотя синтаксис выражений, подобных a = a++ или a++ + a++ является законным, поведение этих конструкций не определено, поскольку в стандарте C не соблюдается. C99 6.5p2 :

  1. Между предыдущей и следующей точкой последовательности объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения. [72] Кроме того, предыдущее значение должно быть считано только для определения значения, которое необходимо сохранить [73]

В сноске 73 далее разъясняется, что

  1. Этот параграф отображает неопределенные выражения операторов, такие как

    i = ++i + 1;
    a[i++] = i;
    

    позволяя

    i = i + 1;
    a[i] = i;
    

Различные пункты последовательности перечислены в Приложении C к C11C99 ):

  1. Ниже приведены точки последовательности, описанные в 5.1.2.3:

    • Между оценками имени функции и фактическими аргументами в вызове функции и фактическим вызовом. (6.5.2.2).
    • Между оценками первого и второго операндов следующих операторов: логическое AND && (6.5.13); логический ИЛИ || (6.5.14); запятая, (6.5.17).
    • Между оценками первого операнда условного? : оператор и в зависимости от второго и третьего операндов (6.5.15).
    • Конец полного декларатора: деклараторы (6.7.6);
    • Между оценкой полного выражения и следующим полным выражением, которое должно быть оценено. Ниже приведены полные выражения: инициализатор, не являющийся частью составного литерала (6.7.9); выражение в выражении выражения (6.8.3); управляющее выражение оператора выбора (if или switch) (6.8.4); управляющее выражение while или do (6.8.5); каждое из (необязательных) выражений оператора for (6.8.5.3); (необязательное) выражение в операторе return (6.8.6.4).
    • Непосредственно перед возвратом функции библиотеки (7.1.4).
    • После действий, связанных с каждым форматированным спецификатором преобразования функции ввода / вывода (7.21.6, 7.29.2).
    • Непосредственно перед и сразу после каждого вызова функции сравнения, а также между любым вызовом функции сравнения и любым перемещением объектов, переданных в качестве аргументов для этого вызова (7.22.5).

Формулировка того же абзаца на С11 :

  1. Если побочный эффект скалярного объекта не зависит от другого побочного эффекта для одного и того же скалярного объекта или вычисления значения с использованием значения одного и того же скалярного объекта, поведение не определено. Если имеется несколько допустимых порядков подвыражений выражения, поведение не определено, если такой необратимый побочный эффект возникает в любом из заказов.84)

Вы можете обнаружить такие ошибки в программе, например, используя последнюю версию GCC с -Wall и -Werror , а затем GCC полностью откажется от компиляции вашей программы. Ниже приведен вывод gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

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

j = (i ++, ++ i);

хорошо определен и будет увеличивать i на единицу, уступая старому значению, отбрасывать это значение; затем в операторе запятой уложите побочные эффекты; а затем увеличивать i на единицу, и полученное значение становится значением выражения - т. е. это просто ухищренный способ написать j = (i += 2) что еще раз является «умным» способом записи

i += 2;
j = i;

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

int i = 0;
printf("%d %d\n", i++, ++i, i);

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


Хотя маловероятно, что какие-либо компиляторы и процессоры действительно это сделают, было бы законно, согласно стандарту C, для компилятора реализовать «i ++» с последовательностью:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

Хотя я не думаю, что какие-либо процессоры поддерживают аппаратное обеспечение, позволяющее эффективно выполнять такую ​​работу, легко представить себе ситуации, когда такое поведение упростит многопоточный код (например, это гарантирует, что если два потока попытаются выполнить вышеописанное последовательность, i бы увеличился на два), и совершенно немыслимо, что какой-то будущий процессор может предоставить что-то подобное.

Если компилятор должен был написать i++ как указано выше (легально по стандарту), и должны были интерпретировать приведенные выше инструкции в ходе оценки общего выражения (также законного), и если бы не было замечено, что одна из других инструкций случилось с доступом к i , было бы (и законным), чтобы компилятор мог генерировать последовательность инструкций, которые могли бы затормозить. Разумеется, компилятор почти наверняка обнаружит проблему в случае, когда одна и та же переменная i используется в обоих местах, но если подпрограмма принимает ссылки на два указателя p и q и использует (*p) и (*q) в вышеприведенном выражении (вместо использования i дважды) компилятору не требуется распознавать или избегать тупиковой ситуации, которая произошла бы, если бы адрес одного и того же объекта был передан как для p и для q .


Я думаю, что соответствующие части стандарта C99 составляют 6.5 выражений, §2

Между предыдущей и следующей точкой последовательности объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения. Кроме того, предыдущее значение должно быть считано только для определения значения, которое необходимо сохранить.

и 6.5.16 Операторы присваивания, §4:

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


Возможно, ваш вопрос не был следующим: «Почему эти конструкции не определяют поведение в C?». Вероятно, ваш вопрос: «Почему этот код (использование ++) не дал мне того значения, которое я ожидал?», И кто-то отметил ваш вопрос как дубликат и отправил вас сюда.

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

Я полагаю, вы уже слышали базовое определение C ++и --операторов и как форма префикса ++xотличается от формы постфикса x++. Но этим операторам сложно думать, поэтому, чтобы убедиться, что вы поняли, возможно, вы написали небольшую небольшую пробную программу, в которой участвовали что-то вроде

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

Но, к вашему удивлению, эта программа не помогла вам понять - она ​​напечатала какой-то странный, неожиданный, необъяснимый результат, предполагая, что, возможно ++, что-то совершенно другое, совсем не то, что вы думали.

Или, возможно, вы смотрите на трудно понятное выражение, например

int x = 5;
x = x++ + ++x;
printf("%d\n", x);

Возможно, кто-то дал вам этот код как загадку. Этот код также не имеет смысла, особенно если вы его запустили - и если вы скомпилируете и запустите его под двумя разными компиляторами, вы, вероятно, получите два разных ответа! Что с этим?Какой ответ правильный? (И ответ в том, что они оба, или ни один из них).

Как вы уже слышали, все эти выражения не определены , а это означает, что язык C не гарантирует, что они сделают. Это странный и удивительный результат, потому что вы, вероятно, думали, что любая программа, которую вы могли бы написать, до тех пор, пока она скомпилирована и запущена, создаст уникальный, четко определенный вывод. Но в случае неопределенного поведения это не так.

Что делает выражение неопределенным? Являются ли выражения вовлеченными ++и --всегда не определены? Конечно, нет: это полезные операторы, и если вы используете их правильно, они отлично определены.

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

Вернемся к двум примерам, которые я использовал в этом ответе. Когда я написал

printf("%d %d %d\n", x, ++x, x++);

вопрос в том, до вызова printf, компилятор вычисляет значение xпервого, или x++, может быть ++x? Но, оказывается, мы не знаем . В C нет правила, в котором говорится, что аргументы функции оцениваются слева направо или справа налево или в каком-либо другом порядке. Поэтому мы не можем сказать , будет ли компилятор сделать xпервый, затем ++x, затем x++, или x++потом ++xпотом x, или какой -либо другой порядок. Но порядок явно имеет значение, потому что в зависимости от того, какой порядок использует компилятор, мы будем явно получать разные результаты printf.

Как насчет этого сумасшедшего выражения?

x = x++ + ++x;

Проблема с этим выражением состоит в том, что он содержит три разные попытки изменить значение x: (1) x++часть пытается добавить 1 в x, сохранить новое значение в xи вернуть старое значение x; (2) ++xчасть пытается добавить 1 в x, сохранить новое значение в xи вернуть новое значение x; и (3) x =часть пытается присвоить сумму двух других обратно x. Какое из этих трех попыток присваивания «выиграет»? Какое из трех значений будет фактически присвоено x? Опять же, и, возможно, удивительно, что в C нет правила говорить нам.

Вы можете себе представить, что приоритет или ассоциативность или оценка слева направо говорит вам, в каком порядке происходят события, но они этого не делают. Вы можете мне не верить, но, пожалуйста, запомните мое слово, и я скажу еще раз: приоритет и ассоциативность не определяют каждый аспект порядка оценки выражения в C. В частности, если внутри одного выражения есть несколько в разных точках, где мы пытаемся присвоить новое значение чему-то вроде x: приоритет и ассоциативность, не сообщают нам, какая из этих попыток происходит первой или последней или что-то еще.

Итак, со всем этим фоном и введением в сторону, если вы хотите, чтобы все ваши программы были четко определены, какие выражения вы можете писать, а какие из них вы не можете написать?

Эти выражения все в порядке:

y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;

Эти выражения не определены:

x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);

И последний вопрос: как вы можете определить, какие выражения хорошо определены и какие выражения не определены?

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

  1. Если есть одна переменная, которая изменяется (назначается) в двух или более разных местах, как вы знаете, какая модификация происходит сначала?
  2. Если есть переменная, которая изменяется в одном месте и имеет значение, используемое в другом месте, откуда вы знаете, использует ли она старое значение или новое значение?

В качестве примера № 1 в выражении

x = x++ + ++x;

есть три попытки изменить «x».

В качестве примера № 2 в выражении

y = x + x++;

мы оба используем значение xи модифицируем его.

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





sequence-points