я = ++ я + ++ я; в C ++ - PullRequest
       29

я = ++ я + ++ я; в C ++

16 голосов
/ 04 декабря 2008

Может кто-нибудь объяснить мне, почему этот код печатает 14? Меня просто спросил другой студент, и я не мог понять это.

int i = 5;
i = ++i + ++i;
cout<<i;

Ответы [ 9 ]

46 голосов
/ 04 декабря 2008

Порядок побочных эффектов не определен в C ++. Кроме того, изменение переменной дважды в одном выражении не имеет определенного поведения (см. C ++ стандарт , § 5.0.0, физическая страница 87 / логическая страница 73).

Решение: не используйте побочные эффекты в сложных выражениях, не используйте более одного в простых. И не мешает включить все предупреждения, которые может выдать компилятор: добавление -Wall (gcc) или /Wall /W4 (Visual C ++) в командную строку выдает соответствующее предупреждение:

test-so-side-effects.c: In function 'main':
test-so-side-effects.c:5: warning: operation on 'i' may be undefined
test-so-side-effects.c:5: warning: operation on 'i' may be undefined

Очевидно, код компилируется в:

i = i + 1;
i = i + 1;
i = i + i;
16 голосов
/ 04 декабря 2008

Это неопределенное поведение, результат будет зависеть от используемого вами компилятора. См., Например, C ++ FAQ Lite .

12 голосов
/ 04 декабря 2008

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

Указанные биты стандарта взяты из текущего стандарта C ++ (ISO / IEC 14882: 2003). В стандарте C есть аналогичная формулировка.

В соответствии со стандартом C ++ изменение значения более одного раза в наборе точек последовательности приводит к неопределенному поведению (раздел 5, параграф 4):

Если не указано иное, порядок оценка операндов индивида операторы и подвыражения отдельные выражения и порядок в котором побочные эффекты имеют место, является не указано.53) Между предыдущими и следующая последовательность указывает скаляр объект должен иметь свое сохраненное значение модифицируется не более одного раза оценка выражения. Кроме того, предыдущее значение должно быть доступ только для определения значения быть сохраненным. Требования этого пункт должен быть соблюден для каждого допустимый порядок подвыражения полного выражения; в противном случае поведение не определено. [Пример:

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 = 7, i++, i++;" определен, поскольку оператор запятой является точкой последовательности.

Вот что в стандарте C ++ означает «неопределенное поведение»:

1.3.12 неопределенное поведение [defns.undefined]

поведение, которое может возникнуть при использовании ошибочной программной конструкции или ошибочных данных, для которых это Международный стандарт не предъявляет никаких требований. Неопределенное поведение также может ожидаться, когда это Международный стандарт опускает описание любого явного определения поведения. [Примечание: допустимо неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время перевод или выполнение программы документированным образом, характерным для среды (с или без выдача диагностического сообщения), чтобы прекратить перевод или выполнение (с выдачей диагностическое сообщение). Многие ошибочные программные конструкции не порождают неопределенное поведение; они должны быть диагностированы. ]

Другими словами, компилятор может делать все, что захочет, включая

  1. выплевывая сообщение об ошибке,
  2. что-то делает реализацию, определенную и задокументированную,
  3. с совершенно непредсказуемыми результатами

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

Так что я предполагаю, что строго говоря, что-то, что демонстрирует неопределенное поведение, не является «незаконным», но по моему опыту, когда в программе на C / C ++ было что-то, демонстрирующее «неопределенное поведение» (если это не расширение) - это ошибка , Я думаю, что называть такую ​​конструкцию незаконной - не смущать, не вводить в заблуждение и не вводить в заблуждение.

Кроме того, я думаю, что попытка объяснить, что делает компилятор для достижения значения 14, не особенно полезна, так как это упускает смысл. Компилятор может делать почти все; на самом деле, вполне вероятно, что компилятор может достичь другого результата, когда он запускается с использованием разных параметров оптимизации (или может привести к сбою кода - кто знает?).

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

Длинный, длинный ответ Стива Саммита (сопровождающего часто задаваемые вопросы о comp.lang.c) по этой теме с 1995 года:

Вот что говорит Бьярн Страуструп по этому вопросу:


Сноска : стандарт C ++ использует слово «незаконно» ровно один раз - при описании различий между C ++ и Standard C в отношении использования static или extern с объявлениями типов.

3 голосов
/ 04 декабря 2008

Простой ... ваш компилятор оценивает ОБА приращений перед выполнением суммы без кэширования промежуточных результатов Это означает, что когда вы добавляете i дважды, он теперь имеет значение 7.

Если вы делаете

int j=++i; 
int k=++i;

i = j+k;

вы увидите 13, как и ожидалось.

2 голосов
/ 04 декабря 2008

На вашем конкретном компиляторе он выбирает сначала выполнить обе операции ++, а затем сложение. Он интерпретирует код как:

int i = 5;
++i;
++i;
i = i + i;
cout << i;

Это совершенно верно.

1 голос
/ 05 декабря 2008

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

я
|
=
|
+
|
унарное выражение - унарное выражение

унарное выражение: унарное выражение оператора

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

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

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

++ я;
++ я;
я = я + я;

Для i = 5 это означает

я = я + 1; // я <- 6 <br> я = я + 1; // я <- 7 <br> я = я + я; // я <- 14 <br>

1 голос
/ 04 декабря 2008

Лучший вопрос был бы, всегда ли это будет 14?

int i = 5;
i = ++i + ++i;
cout<<i;

i = ++i   + ++i   ;
i = ++(5) + ++(5) ;
i =    6  +    6  ;
i = 12;

i = ++i   + ++i   ;
i = ++i   + ++(5) ;
i = ++i   +   (6) ;
i = ++(6) +    6  ;
i =   (7) +    6  ;
i = 13;

i = ++i   + ++i   ;
i = ++i   + ++(5) ;
i = ++(6) +   (6) ;
i =   (7) +   (7) ;
i = 14;

По всей вероятности, это, вероятно, будет 14, потому что это делает немного больше смысла.

0 голосов
/ 04 декабря 2008
 i = i++ + i; //11  

 i = i++ + i++; //12

 i = i++ + ++i; //13

 i = ++i + i++; //13

 i = ++i + ++i; //14    
0 голосов
/ 04 декабря 2008

Поскольку приращение префикса имеет приоритет:

int i = 5;
i = i+1; // First ++i, i is now 6
i = i+1; // Second ++i, i is now 7
i = i + i // i = 7 + 7
cout << i // i = 14
...