Вопрос

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

int i = 5;
i = ++i + ++i;
cout<<i;
Это было полезно?

Решение

Порядок побочных эффектов не определен в C ++. Кроме того, изменение переменной дважды в одном выражении не имеет определенного поведения (см. стандарт C ++ , & # 167; 5.0.4, физическая страница 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;

Другие советы

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

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

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

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

За исключением отмеченных случаев, порядок вычисления операндов отдельных операторов и подвыражений отдельных выражений, а также порядок , в котором происходят побочные эффекты, не указан.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 ++ и Стандартным C в отношении использования static или extern с объявлениями типов.

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

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

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

i = j+k;

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

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

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

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

Лучший вопрос: всегда ли это будет 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;

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

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

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

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

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

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

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

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

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

i = i + 1;//я <- 6
i = i + 1;//я <- 7
i = i + i;//я <- 14

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

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
 i = i++ + i; //11  

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

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

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

 i = ++i + ++i; //14    
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top