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

вторник, ноября 15, 2005

Точки следования (sequence points)

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

О таинственном. Я пишу программу
int x=0;
x=x++;
cout<<x;

Вопрос: что выведется на экран? Ну что может быть проще - сейчас запустим да проверим. Я запустила MSVC++6.0, ответ получился 1.
Потом запустила gcc 3.4.4 и получился 0.
Если бы запустила что-то еще, то могло получиться нечто третье...
Кто из них прав? На самом деле все. А вот почему так в двух словах и не скажешь...

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

Точки следования (sequence points) - это некие точки в программе, где состояние реальной программы полностью соответствует состоянию абстрактной машины, описанной в Стандарте. С помощью точек следования стандарт объясняет, что может, а чего не может делать компилятор и что нам нужно сделать, чтобы написать корректный код.

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

Если код выглядит так:
int x;
x = 3;
cout << x << endl;
x = 4;

То нет никаких сомнений, что на печать будет выведено 3. Точка с запятой в конце выражения "x = 3;" как и любая точка с запятой в конце любого выражения - это точка следования. Перед тем как любой код после этой точки с запятой выполнится, побочный эффект от сохранения значения 3 в int объект x должен быть полностью завершен. И мы точно знаем, что вывод не будет равен 4, потому что вывод полностью завершен к тому моменту, как выполнение программы достигнет точки с запятой в конце выражения "cout << x << endl;". Побочный эффект от сохранения значения 4 в x в следующем выражении еще не случился.

Где находятся точки следования


  1. В конце каждого полного выражения(Глава Стандарта 1.9/16). Обычно они помечены точкой с запятой ;

  2. В точке вызова функции (1.9/17). Но после вычисления всех аргументов. Это и для inline функций в том числе.

  3. При возвращении из функции. (1.9/17) Есть точка следования сразу после возврата функции, перед тем как любой другой код из вызвавшей функции начал выполняться.

  4. (1.9/18) После первого выражения (здесь оно называется 'a') в следующих конструкциях:

    a || b

    a && b

    a , b

    a ? b : c

    Вычисление здесь идет слева направо. То есть левое выражение (по имени 'a') вычисляется и все побочные эффекты от такого вычисления завершаются. Потом, если все значение всего выражения известно, правое выражение не вычисляется, иначе вычисляется.
    Правило слево-направо не работает для переопределенных операторов. В этом случае переопределенный оператор ведет себя как обычная функция. Еще такой момент - ?: не может быть переопределен по стандарту.

    (Упомянутая запятая это оператор запятая. Она не имеет никакого отношения к запятой, разделяющей аргументы функции.)


Если рассмотреть пример "x = x++;", то здесь имеет место попытка изменить объект дважды.
Если программа пытается модифицировать одну переменную дважды не пересекая точку следования, то это ведет к undefined behavior. Так говорит Стандарт. И тут же в Стандарте приводятся примеры.

1) i = v[i++]; //unspecified behavior
2) i = 7, i++, i++; //i равно 9

3) i = ++i + 1; //unspecified behavior
4) i = i + 1; //значение i увеличено на 1

Что-то с чем-то не сходится. В первом и третьем примерах указано "unspecified behavior", хотя переменная изменяется дважды и только что было сказано, что это ведет к undefined behavior.
И точно в списке багов Стандарта это место упомянуто. В предложении по устранению бага советуют поменять слово unspecified на слово undefined.
Но на самом деле не так уж и важно, unspecified или undefined behavior. Важно то, что непонятно, что именно получится в итоге. Поэтому не надо модифицировать переменную больше одного раза между двумя точками следования, поскольку это может привести к весьма неожиданным последствиям. Вот, например
int x;
x = 3;
cout << (++ x + ++ x) + ++ x << endl;
x = 3;
cout << ++ x + (++ x + ++ x) << endl;

gcc 3.4.4:
16
18
Получилось, что операция сложения не ассоциативна!

MSVC++6.0:
18
18

Примеры:
i = ++i; // undefined behavior, переменная модифицируется дважды
i = i + 1; // все в порядке
i ? i=1 : i=5; // все в порядке (там, где знак ? есть точка следования, а потом выполнится лишь одно из выражений)
i=1; i++; // все в порядке (после каждого выражения находится точка следования)
i=1, i++; // все в порядке (на операторе запятая находится точка следования)

Пример с функциями:
void f(int, int);
int g();
int h();
f(g(), h());
По Стандарту неизвестно, какая из функций g или h будет вызвана первой, но известно, что f() будет вызвана последней.


Разработчикам gcc периодически сабмитят баги по этому поводу, в итоге в
Frequently Reported Bugs in GCC было внесено:
Результат следующих операций непредсказуем и это полностью соответствует стандарту
x[i]=++i
foo(i,++i)
i*(++i) /* special case with foo=="operator*" */
std::cout << i << ++i /* foo(foo(std::cout,i),++i) */

Вот пример такой баги: Bug13403. Их таких там очень много.

В следующей программе
#include <iostream>
int main(){
int i=0;
i = 6+ i++ + 2000;
std::cout << i << std::endl;
return 0;
}

Результат единица! А должен быть 2006.
Если убрать "i++", то результат правильный ("2006"). Но пока есть "i++" внутри выражения, я могу делить, умножать, вычитать, складывать, все равно результат всегда "1".
Например в выражении
i = (6+ i++ + 2000)/2;
"i" все равно единица.

Но если я заменю постфикс "i++" префиксом "++i" тогда все считается корректно.


Ну отвечают на это все - "это undefined behavior по Стандарту" и баг закрывают.

Несколько присваиваний подряд
Интересный момент с таким кодом:
int a=1, b=2, c=3;
a=b=c=0;
Стандарт как-то очень нечетко описывает такую ситуацию. Он говорит, что операция должна происходить справа налево, но ничего не говорит дополнительно по поводу того, когда должен быть результат этой операции записан в переменную. Точек следования внутри выражения нет, что значит, что компилятор может теоретически творить здесь что угодно и не обязательно все переменные в итоге будут равны 0. Так что, опять же теоретически, здесь имеет место unspecified behavior. По этому поводу периодически рождаются хвостатые флеймы в comp.lang.c++.moderated. Но на самом деле я не слышала о компиляторе, который делал бы в такой ситуации присвоение не справа налево. Вопрос этот известен, а компиляторы не садисты пишут.

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

Ссылки по теме:
Разница между unspecified behavior и undefined behavior
comp.lang.c++.moderated Undefined behavior?? I think not!
comp.lang.c++.moderated postfix increment and assignment
comp.lang.c++.moderated fresh look on copying objects and passing args by value
comp.lang.c++.moderated Evaluation precedence questions
comp.lang.c++.moderated = Operator?
comp.lang.c++.moderated How defined is undefined behaviour?
comp.lang.c++.moderated Case for post-increment/decrement
comp.lang.c++.moderated What is a sequence point?
comp.lang.c++.moderated Sequence point and evaluation order
comp.lang.c++.moderated sequence point and reference
comp.lang.c++.moderated sequence points and , operator

54 коммент.:

Анонимный комментирует...

Ага, подробно не разбирали. Ну, просто говорили, что "неопределенное поведение" (запомните это, и никогда так не делайте), а точки следования, да, не упоминали.
Забавно другое: что вопросы вида
i++ + ++i (написанные i+++++i) или что значит ++i++ (тут, правда, уже и другое примешивается) часто задавали на собеседованиях. При этом услышать хотели вовсе не "неопределенное поведение" (и тем более не точки следования), а именно про префиксный и постфиксный инкременты, порядок следования и т.д. И даже если скажешь: поведение неопределенное, все равно нужен был ответ ("ну компилятор же все-таки это как-то вычислит, вот и напишите про порядок операций и т.д. и т.д.").
Впрочем, это уже о не совсем корректных задачах на собеседованиях на работу...
// tensor

Sergey Petrov комментирует...

Все замечательно, только ты слово "undefined" несколько раз написала как "underfined". Просто обращаю внимание :)

Maniac комментирует...

Когда я вижу слово "Стандарт" - вот так с большой буквы, то у меня непреодолимо напрашиваются ассоциации, что это надо читать как "Святая Библия" :-)

А "Глава Стандарта 1.9/16" и вообще подозрительно смахивает на "Иезекиль, 25:17" :-)

Alena комментирует...

2tensor:
А, кстати, интересно, что делать в таких ситуациях? Упорствовать или сказать то, что хочет услышать собеседник? Я думаю, я бы вежливо, но твердо, настояла на своем...
Да и я знаю, здесь есть люди, которые сами проводят собеседования. Как бы вы отнеслись к тому, что соискатель указал вам на ваше давнее заблуждение?

2Sergey Petrov:
Спасибо, поправила.

2Maniac:
Ладно тебе зубоскалить, ты так же относишься к веб-стандартам. :-)

Анонимный комментирует...

А, кстати, интересно, что делать в таких ситуациях?
Упорствовать или нет - сложно сказать, особенно если собеседование. Мне приходилось в инете поправлять высказывания или статьи - так даже давая ссылки на источники, убедительные примеры, замечаю, что большинство ну оочень неохотно признает ошибки. А на собеседовании, где и книжки может не быть (куда ткнуть), и к интернету скорее всего не пойдут искать и проверять...
но выводы у меня твердо сделаны: если человек ошибается в том, о чем сам спрашивает соискателя (о чем сам заговорил), то такая работа пойдет лесом. Ибо с такими людьми дальше только хуже будет.

Как бы вы отнеслись к тому, что соискатель указал вам на ваше давнее заблуждение?
Да, да, смелее! Все дружно признаемся, как умеем адекватно реагировать на свои собственные ошибки ;)
// tensor

Анонимный комментирует...

Угу.
Заказчик недавно тестил наш компилятор. Прислал баг-репорт со следующим текстом:

for (int i=0; i<5; i=i++)
// do something

и долго ругался, что зацикливается это. Пришлось все в посте описанное объяснять тестеру. ;)

alex_shubert комментирует...

Мой любимый тест. Меня так самого 5 лет назад принимали.
Испытуемому задается вопрос. Он отвечает. После чего вы с начальственным апломбом указываете на мнимую ошибку.

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

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

Рома комментирует...

По-моему, со случаем
a = b = c = d;
всё вполне прозрачно, потому что это

a::operator = (b::operator = (c::operator = (d)));

elide комментирует...

после прочтения сего замечательного поста вспомнил про вопрос, который давно не давал мне покоя. если взять код
int i = 0;
std::cout << i++ << i++ << i++;
то по своей сути, он представляет из себя
int i = 0;
( ( std::cout.operator<<(i++) ).operator<<(i++) ).operator<<(i++);
т.е. если и вызов функции, и возврат из нее есть точки следования, то этот код валиден и должен выводить "012", однако он выводит "210" на всех доступных мне платфорах.
внимание, вопрос: это я идиот и так и должно быть? или компиляторы глючат?

elide@bk.ru

Alena комментирует...

т.е. если и вызов функции, и возврат из нее есть точки следования, то этот код валиден и должен выводить "012"

У меня в посте есть аналогичный пример с сайта gcc.
std::cout << i << ++i

Что раскладывается в
foo(foo(std::cout,i),++i)

где функция foo в данном случае - это operator<<.

Порядок вычисления аргументов функции может быть любым.

или компиляторы глючат?

Если код проверен на нескольких компиляторах, то вряд ли они все будут глючить, причем совершенно одинаково. Плюс, есть же списки известных багов. В случае gcc, у них вообще доступ в багтрак открыт для всех желающих, подавляющее большинство багов известно. http://gcc.gnu.org/bugzilla/
Можно, конечно, напороться на нечто, что еще никто не находил, но вероятность очень невелика.

Spalex комментирует...

Здравствуйте, Алёна! Не подскажете, ссылку на место в Стандарте, где написано, что "Правило слево-направо не работает для переопределенных операторов". Верю, но вот найти пока не могу :(.

Alena комментирует...

Здравствуйте, Алёна!

Приветствую!

Не подскажете, ссылку на место в Стандарте, где написано, что "Правило слево-направо не работает для переопределенных операторов". Верю, но вот найти пока не могу :(.

Прямо такой фразы дословно там нет.
Там так: в пятой главе рассказывается о встроенных операторах, где говорится, что для них правило "слева направо" выполняется.
В главе 13.5 рассказывается о перегрузке операторов. Где упоминается, что перегрузка осуществляется с помощью operator function. Поскольку это уже функции, то и правила для них работают те, что для функций, а не те, что для встроенных операторов. И там не только правило "слева направо" перестает работать. Например, если перегрузить оператор ++, то на нем появится точка следования.

Alena комментирует...

2Spalex:

Только что совершенно случайно наткнулась: очень подробное и хорошее объяснение про эти операторы есть у Мейерса в More Effective C++.
Item 7: Never overload &&, ||, or ,.

Spalex комментирует...

Спасибо за наводочку! ;)

Анонимный комментирует...

Кстати, если серьезно к этому подходить: ++ ++ iter — тоже UB! :-)

Роман.

P. S. Мне почему-то немецкий интерфейс подсунули… Ну и что, что IP из Германии?

P. P. S. Шлите мне спам: dodge_this@qwertty.com

MasterZiv комментирует...

Аллёна! Ты мой куммир ! Ты раскрыла людям глаза ! Теперь фсе будут знать, что такое точки следования в С !

Анонимный комментирует...

>a=b=c=0;

c=0 - это выражение, значение которого равно нулю. точно такое же, как например ++i

archimed7592 комментирует...

Для ++i в Стандарте явно сказано, что результатом будет новое значение. Что же касается оператора присваивания, то этого там вовсе не сказано - сказано, что результатом будет сохранённое значение. Когда же происходит сохранение(side effect) - неизвестно(точнее известно, но не с достаточной точностью, чтобы гаарнтировать результат).
Т.о. даже очень часто используемая конструкция
if ((result = f()) == -1) { ... }
Представляет из себя UB(IS-5/4).

Для неверующих вот цитата из Стандарта про оператор присваивания:
IS-5.17/1 Assignment operators
There are several assignment operators, all of which group right-to-left. All require a modifiable lvalue as their left operand, and the type of an assignment expression is that of its left operand. The result of the assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue.

Насчёт того, когда именно "assignment" будет "taken place" читаем IS: 1.9/7-18, 5/1-4 или читаем пост Алёны про sequence points и последовавшую за ним дискуссию.

Анонимный комментирует...

to archimed7592

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

Алёна комментирует...

2Анонимный:
Так и не важно когда именно произойдёт присваивание. Стандарт требует, чтобы результатом выражения всегда было то значение, которое присвоится.

Я предлагаю не продолжать эту дискуссию, потому что продолжать ее можно бесконечно. Чтобы увидеть всю аргументацию обеих сторон можно почитать любой флейм на comp.lang.c++.moderated. Ссылки в посте есть.

Анонимный комментирует...

x +=1; //?
x =++x; UB
x =x+1; //Норма

Почему во втором случае UB, а в 3 норма и как трактовать 1 вариант?
ведь во всех 3 случаях логически одно и тоже (сложение потом присваивание)

И как можно трактовать вот это выражение?
int x =-1;
if (++x) x=x
else x=-x;

если такой вариант вполне gприемлем
x =(++x)?x:-x;

Алёна комментирует...

x +=1; //?
x =++x; UB
x =x+1; //Норма

Почему во втором случае UB, а в 3 норма и как трактовать 1 вариант?
ведь во всех 3 случаях логически одно и тоже (сложение потом присваивание)


Нет, во втором случае попытка изменить переменную x дважды. ++x - тоже подразумевает присваивание.

Первый вариант - просто прибавить единицу. Модификация одна.

И как можно трактовать вот это выражение?
int x =-1;
if (++x) x=x
else x=-x;


Эээ... А что не так с выражением?

Анонимный комментирует...

И как можно трактовать вот это выражение?
int x =-1;
if (++x) x=x
else x=-x;

Эээ... А что не так с выражением?

А что не так - то?

Анонимный комментирует...

В следующей программе
#include .....
int main(){
int i=0;
i = 6+ i++ + 2000;
std::cout << i << std::endl;
return 0;
}

Результат единица! А должен быть 2006.
Если убрать "i++", то результат правильный ("2006"). Но пока есть "i++" внутри выражения, я могу делить, умножать, вычитать, складывать, все равно результат всегда "1".
Например в выражении
i = (6+ i++ + 2000)/2;
"i" все равно единица.

Но если я заменю постфикс "i++" префиксом "++i" тогда все считается корректно.

Ну отвечают на это все - "это undefined behavior по Стандарту" и баг закрывают.

Разумеется единица!
i увеличили после окончания операции (его сохранённое значение) и записали на место получившегося было 2007 после окончания операции.
Чего удивительного? Удивительно было бы ожидать увидеть 2007...
Вот тогда действительно следовало бы репу долго чесать...
А если ++i тогда действительно 2007.
Никакой мистики. Просто нужно хотя бы приблизительно понимать, как реально осуществляет операции скомпилированный код.

Flint комментирует...

Добрый день.
я правильно понял, что из логических операций точками следования являются только || и &&, а все остальные нет?

Алёна комментирует...

Flint
я правильно понял, что из логических операций точками следования являются только || и &&, а все остальные нет?

Угу, я не вижу в Стандарте упоминаний про точки следования в других логических операциях.

Zefick комментирует...

Забавно другое: что вопросы вида
i++ + ++i (написанные i+++++i) или что значит ++i++ (тут, правда, уже и другое примешивается) часто задавали на собеседованиях. При этом услышать хотели вовсе не "неопределенное поведение" (и тем более не точки следования), а именно про префиксный и постфиксный инкременты, порядок следования и т.д.

Когда вам на собеседовании задают этот вопрос, слышать хотят, конечно, не "неопределённое поведение" и даже не про порядок следования, а про то, что это семантическая ошибка. (++i) - не l-value, поэтому ни складывать не инкрементировать его дальше нельзя.

VinSmile комментирует...

Zefick
>> (++i) - не l-value, поэтому ни складывать не инкрементировать его дальше нельзя.

Я так понимаю речь про ++i++. Если да, то тогда уже не (++i) являеться помехой а (i++), так как приоритет у него больше... Кстати на MS С++ (++i) самое настоящее l-value. Поэтому такой вот код будет работать:
(++i)++
Чего нельзя сказать про C#. Там это не скомпилируеться.

VinSmile комментирует...

Ах да :) Забыл поблагодарить за статью, она ответила на многие мои вопросы. Спасибо огромное!!! И конечно же с восьмым марта! Которое, правда, уже час как девятое :)

Алёна комментирует...

2VinSmile:

Ах да :) Забыл поблагодарить за статью, она ответила на многие мои вопросы. Спасибо огромное!!! И конечно же с восьмым марта!

Спасибо :-)

Которое, правда, уже час как девятое :)

Да ничего страшного :-)

Анонимный комментирует...

Что-то я запутался =)

Вот есть f(g(), h());
По Стандарту неизвестно g или h будет вызвана первой.
Предположим, g.
Вызов функции - точка следования.
В точке следования состояние реальной программы полностью соответствует состоянию абстрактной машины, описанной в Стандарте.
Получается, Стандартом определено, что первой будет вызвана g.

Алёна комментирует...

2Анонимный:


Вот есть f(g(), h());
По Стандарту неизвестно g или h будет вызвана первой.
Предположим, g.
Вызов функции - точка следования.
В точке следования состояние реальной программы полностью соответствует состоянию абстрактной машины, описанной в Стандарте.
Получается, Стандартом определено, что первой будет вызвана g.


В начале было сделано странное предположение "Предположим, g." Потом на его основе делаются выводы, что неправильно.

Mishgan комментирует...

#include
int main(){
int i=0;
i = 6+ i++ + 2000;
std::cout << i << std::endl;
return 0;
}


MSVS 2008 дает результат 2007 8-)

Анонимный комментирует...

Автор +

Анонимный комментирует...

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

Evg комментирует...

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

В виде некоторого итога писалось следующее:

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

Далее приводится пример на UB:

> x[i]=++i

В нём переменная i модифицируется всего один раз (т.е. мы не попадаем в указанное выше ограничение) и тем не менее остаётся UB. Можно вообще написать проще:

j = i + i++;

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

Поэтому предлагаю скорректировать формулировку. Добавить (или даже заменить) что-то типа "если между двумя точками следования есть запись в переменную, то переменная между этими точками может быть использована только один раз, в противном случае мы придём к UB"

С уважением, Evg.

Alena комментирует...

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

Поэтому предлагаю скорректировать формулировку.


Полная корректная формулировка есть в начале - "все побочные эффекты кода...", далее разбирается частный случай про две модификации.

Evg комментирует...

> Полная корректная формулировка есть в
> начале - "все побочные эффекты
> кода...", далее разбирается частный
> случай про две модификации.

Я так понимаю, что статья в первую очередь направлена на "новичков". На мой взгляд 9 из 10 начинающих из этой фразы НЕ сделают вывод в той формулировке, о которой я писал выше. А вывод сделают именно из той фразы, где говорится про две записи в переменную.

Я не навязываю свои идеи. В инете слишком мало статей, в которых действительно человеческим языком (а не для галочки) разжёваны неочевидные для начинающих (и не только начинающих) моменты. Твоя статья - одна из них, а потому не могу равнодушно пройти мимо. Просто хотелось бы более чёткой формулировки, сохраняя при этом её неформальность. И в обязательном порядке сохраняя понятность для того, кто видит статью в первый раз

Анонимный комментирует...

int a=0;
printf("%d",a,a++,a++,a++,a++,a++,a++);

относится ли данный код к UB?

Alena комментирует...

Анонимный
int a=0;
printf("%d",a,a++,a++,a++,a++,a++,a++);


Да, аналогично "примеру с функциями"

Waterfall комментирует...

Вообще говоря эти побочные эффекты - стрёмная штука. Функциональные языки в этом плане проще =)

aamonster комментирует...

2 Waterfall: проще-то они проще, но там свои заморочки... не даром в них таки вводят присваивания и т.п. (правда, стараются хоть как-то формализовать это дело... у Пирса в книжке "Типы в языках программирования" вроде хорошо описано)
Ну и не для любой задачи хороши, иначе б давно вытеснили императивные.

Waterfall комментирует...

да, все верно. всему свое применение

vaychick комментирует...

Mishgan
int i=0;
i = 6+ i++ + 2000;
результат 2007 (MinGW в Qt 4.7.0 под Win)

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

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

Хотя на моей системе видать произошло другое, значение 2006 затерло старое значение 0 и потом его и инкрементировал post-инкремент.

Анонимный комментирует...

как насчёт выражений внутри if или while после них есть точка следования? Можно ли гарантировать при вхождении в блок все сайд-эффекты завершены?

Sergey D комментирует...

Недавно столкнулся с подобным вопросом на собеседовании. Пришлось достаточно упорно доказывать, почему в выражении "i += ++i + i + i++;" имеет место быть неопределенное поведение. Видимо, опять же, собеседующий надеялся услышать какой-то конкретный ответ.

Сергей комментирует...

Пожалуйста объясните мне правильно ли я понял:
1)получается что и = и i++, хоть и находятся в таблице предпочтений, но фактически занимают там "незаконные" места т.к. реально заканчивают своё выполнение точно не известно когда?
2)Или таблица предпочтений операторов (я тогда работал только под MSVS) это всегда нечто компайлер-специфик?
3) Почему в случае с ++i неопределённость пропадает, ведь если не было точки следования, где гарантия что запись увеличенного значения произойдёт сразу а не потом?

Alena комментирует...

Сергей

1)получается что и = и i++, хоть и находятся в таблице предпочтений, но фактически занимают там "незаконные" места т.к. реально заканчивают своё выполнение точно не известно когда?

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

2)Или таблица предпочтений операторов (я тогда работал только под MSVS) это всегда нечто компайлер-специфик?

Не специфик, порядок выполнения операций определен в Стандарте.

3) Почему в случае с ++i неопределённость пропадает, ведь если не было точки следования, где гарантия что запись увеличенного значения произойдёт сразу а не потом?

В смысле, в случае с ++i; ? Точка следования есть на точке с запятой. В этот момент всё фиксируется.

Анонимный комментирует...

Undefined behavior - это что? Компилятор может вместо этого назначить код очистки диска C:, если ему захочится?

Alena комментирует...

Анонимный

Undefined behavior - это что? Компилятор может вместо этого назначить код очистки диска C:, если ему захочится?

Вот тут у меня про это было подробно
http://alenacpp.blogspot.com/2005/08/unspecified-behavior-undefined.html

Анонимный комментирует...

2)Или таблица предпочтений операторов (я тогда работал только под MSVS) это всегда нечто компайлер-специфик?

Приоритет и порядок выполнения задает КАКОЙ ОПЕРАТОР БУДЕТ ИСПОЛЬЗОВАТЬ РЕЗУЛЬТАТ КАКОГО ОПЕРАТОРА.

Но о побочных эффектах выполнения оператора (т.е. то, что делает опрерато кроме вычисления результата. Пример
a=10; c=(a++ +1); //у того что в скобках побочный эффект увеличение a,
результ равен 11 )

ПРИМЕР:
A + ++B * C
Приоритет задает только то, что РЕЗУЛЬТАТ (++B) умножится на C
и РЕЗУЛЬТАТ умножения сложится с A

Но стандарт допускает, что побочный эффект (изменение переменной B) произойдет хоть в самом начале, хоть в конце хоть в середине.

Анонимный комментирует...

Правильно ли я понимаю, что вызов перегруженного оператора содержит точку следования?
В таком случае, если x -- переменная некоторого пользовательского класса с соответствующими перегруженными операторами, то примеры типа
(++x + ++x) + ++x
уже не undefined, а unspecified. Т.е. непонятно в каком порядке будут вычисляться операнды, но понятно, что после каждого вычисления побочные эффекты фиксируются. А если к тому же оператор + коммутативен, ассоциативен и не имеет побочных эффектов (вызов + не сказывается на результате ++x), то результат и вовсе однозначно определен. Или я ошибаюсь?

GrayFace комментирует...

С a=b=c=0 все однозначно занулятся. Нельзя сказать только, в каком порядке они занулятся. В том числе в ссылке https://groups.google.com/forum/#!topic/comp.lang.c++/VJhvpgWG554 это расписано.
"The result of the
assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue." - значит, значение, которое было до присвоения, ну никак не может быть возвращено.

Еще яснее в стандарте: http://www.rsdn.ru/forum/cpp/2321838.1
: это либо a = 0; b = 0;, либо b = 0; a = 0;. Аналогично с a=b=c=0.
"The result of the
assignment operation is the value stored in the left operand after the assignment has taken place; the result is an lvalue"

Разницу между ними можно почуствовать только с объеком, переопределяющим оператор присваивания.

panqnik комментирует...

I've written a blog post about this topic, here: http://blog.szulak.org/dev/sequence-points-c/
Would you mind and leave your feedback, please?

regards,
szulak