Сколько будет i++ + ++i?
За Write-only Code нужно отрывать руки
Вопрос для собеседования на вакансию C/C++-программиста:
int i = 5, j = i++ + ++i; – чему равно i и j?
Ответ вида «За такое нужно руки отрывать», не подходит, ибо автор вопроса считает, что знает правильный ответ — i=7, j=12. Но так ли это?
Автор приводит следующую аргументацию:
Данная конструкция вполне приемлемая, в результате будет i=7, j=12 ((5+1) + 6, затем i увеличивается).
На самом деле, 7 и 12 — это один из нескольких возможных вариантов. А всё потому, что перед тем, как задавать такие вопросы, нужно читать стандарт языка.
В стандарте языка программирования C есть понятие точек следования (по-буржуйски — sequence points). Если говорит русским языком, точки следования — это места, в которых гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих вычислений еще не проявились. Это очень важно для вычисления выражений, в которых конечный результат выражения зависит от порядка вычислений подвыражений. Добавление точек следования является одним из методов обеспечения гарантированного значения результата, потому что они ограничивают способы, которыми выражение может быть вычислено.
В C/C++ ни оператор сложения, ни операторы префиксного/постфиксного инкремента не являются точками следования. К тому же, стандарт говорит следующее:
Between the previous and next sequence point an 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
Иными словами, из двух операций i++ и ++i только одна приведёт к изменению i. И в общем случае результат вычисления неоднозначных выражений не определён. Мило.
Но на практике компилятор руководствуется приоритетом операций и здравым смыслом. В частности, если скормить gcc подобную программку, её ассемблерный вариант будет выглядеть примерно так (я его привёл в читабельный вид):
add DWORD PTR [i], 1
mov eax, DWORD PTR [i]
add eax, eax
mov DWORD PTR [j], eax
add DWORD PTR [i], 1
Иными словами, получилось так:
i = 5;
i = i + 1;
tmp = i;
j = tmp + tmp;
i = i + 1;
В соответствии с приоритетом операций компилятор сначала вычисляет выражение ++i, потом складывает результат с самим собой (получает j) и увеличивает i на единицу (постфиксный инкремент).
Проблема здесь в том, что стандарт говорит, что до достижения точки следования i может измениться только один раз. Только это надо доказать
Есть очень красивое доказательство.
int main(void)
{
int i;
scanf("%d", &i);
int j = i++ + ++i;
printf("%d %d\n", i, j);
return 0;
}
Скомпилируем и запустим программу. В первом случае безо всяких оптимизаций, во втором — со включённой оптимизацией:
5
6 11
$ gcc test.c -O3 -o test && ./test
5
7 12
Что и требовалось доказать: при разном уровне оптимизации получим разный результат.
Теперь объявим переменную статической:
int main(void)
{
static int i=5;
int j = i++ + ++i;
printf("%d %d\n", i, j);
return 0;
}
и скомпилируем/запустим оба варианта:
5
6 11
$ gcc test.c -O3 -o test && ./test
5
7 12
Опять при одинаковых входных данных получаем разный результат.
На закуску: уберём static, добавим volatile, скомпилируем и запустим:
5
6 11
$ gcc test.c -O3 -o test && ./test
5
6 11
Вывод: результат зависит от того, какие оптимизации компилятор может применить. Кому интересно
— можно поэкспериментировать с алиасингом и указателями. Так что такое вот выражение является ярким примером write-only кода, за который, как известно, нужно отрывать руки
Апр
2009
Комментарии к статье «Сколько будет i++ + ++i?» (5) »
Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.
Оставить комментарий к записи «Сколько будет i++ + ++i?»
गते गते पारगते पारसंगते बोधि स्वाहा
Меня зовут Владимир, я программист-фрилансер, специализирующийся на Web-программировании и програмировании под Linux.
По совместительству занимаюсь администрированием LAMP/LNMP-серверов и техническим переводом.


Уважаемый Владимир!
Аналогичные Вашему вопросы я регулярно задаю своим студентам на контрольных модулях по С++. К сожалению, мало кто отвечает. Прилагаю реализацию на VC++ 2005. Все три варианта реализации дают один и тот же результат.
Вопрос: какой версией gcc Вы пользовались?
gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3
Visual C++ (особенно 2005) не очень-то славится поддержкой стандарта.
Да, Владимир, это правда. Но сколько людей его используют! Поэтому нужно знать его сильные и слабые стороны. А gcc, конечно, ОЧЕНЬ продвинутый и развивающийся компилятор, но мои студенты, к сожалению, его практически не знают.
А это результат gcc-3.4.5 (без оптимизации)
Enter value static int i= 5
i=7 j=12
Enter value int i= 5
i=7 j=12
Enter value volatile int i= 5
i=6 j=11
Вообще целью статьи было показать, что использование кода, поведение которого не определено стандартом, является очень плохой идеей: результаты меняются даже в разных версиях одного и того же компилятора и сильно зависят от того, какие оптимизации компилятор может применить.