Теоретический вопрос про cout << a++ << a++;
От: Кодёнок  
Дата: 07.10.05 10:35
Оценка:
// 1. В поиске я нашёл, что это UB.
cout << a++ << a++;

// 2. А так?
cout.print(a++).print(a++);

// 3. А вот так?
cout.operator <<(a++).operator <<(a++);


Пусть cout — класс, имеющий оператор << и функцию print, которые реализованы абсолютно одинаково, и возвращают ссылку на this.
Re: Теоретический вопрос про cout << a++ << a++;
От: Глеб Алексеев  
Дата: 07.10.05 10:42
Оценка: 25 (3) -1
Здравствуйте, Кодёнок, Вы писали:
Кё>
Кё>// 1. В поиске я нашёл, что это UB.
Кё>cout << a++ << a++;
Кё>// 2. А так?
Кё>cout.print(a++).print(a++);
Кё>// 3. А вот так?
Кё>cout.operator <<(a++).operator <<(a++); // эквивалентно первому случаю
Кё>

Кё>Пусть cout — класс, имеющий оператор << и функцию print, которые реализованы абсолютно одинаково, и возвращают ссылку на this.
Дело не в функции print или операторе <<, а в том, что в выражении между 2-мя инкрементами переменной a нет точки следования. Компилятор вправе вычислить оба инкремента до вызова первого print.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Теоретический вопрос про cout << a++ << a++;
От: Radmir Россия  
Дата: 07.10.05 10:51
Оценка: -4 :))) :)))
Здравствуйте, Кодёнок, Вы писали:

Кё>
Кё>// 1. В поиске я нашёл, что это UB.
Кё>cout << a++ << a++;
Кё>


Кё>// 2. А так?

Кё>
Кё>cout.print(a++).print(a++);
Кё>

так нельзя т.к. print это функция. мы можем следать только так.
cout.print(a++);
cout.print(a++);


Кё>// 3. А вот так?

Кё>
Кё>cout.operator <<(a++).operator <<(a++);
Кё>

во первых "operator <<" не пишется а записывается просто <<
соответственно имеем
cout << a++ << a++; (если конечно убрать "." которые являются в данном случае синтаксические ошибки).


Кё>Пусть cout — класс, имеющий оператор << и функцию print, которые реализованы абсолютно одинаково, и возвращают ссылку на this.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Лучше спросить дорогу чем заблудиться
Re: Теоретический вопрос про cout << a++ << a++;
От: Кодт Россия  
Дата: 07.10.05 11:04
Оценка: 10 (1) +1
Здравствуйте, Кодёнок, Вы писали:

Кё>
Кё>// 1. В поиске я нашёл, что это UB.
Кё>cout << a++ << a++;

Кё>// 2. А так?
Кё>cout.print(a++).print(a++);

Кё>// 3. А вот так?
Кё>cout.operator <<(a++).operator <<(a++);
Кё>


Кё>Пусть cout — класс, имеющий оператор << и функцию print, которые реализованы абсолютно одинаково, и возвращают ссылку на this.


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

Кстати, operator<< — это не член ostream, а внешняя функция.
operator<<( // второй
  operator<<( // первый
    cout,
    a++
  ),
  a++
);
Перекуём баги на фичи!
Re[2]: Теоретический вопрос про cout << a++ << a++;
От: Глеб Алексеев  
Дата: 07.10.05 11:06
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Кстати, operator<< — это не член ostream, а внешняя функция.

Для встроенных типов это именно член ostream.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: Теоретический вопрос про cout << a++ << a++;
От: Кодёнок  
Дата: 07.10.05 11:11
Оценка:
Здравствуйте, Глеб Алексеев, Вы писали:

Кё>>Пусть cout — класс, имеющий оператор << и функцию print, которые реализованы абсолютно одинаково, и возвращают ссылку на this.

ГА>Дело не в функции print или операторе <<, а в том, что в выражении между 2-мя инкрементами переменной a нет точки следования. Компилятор вправе вычислить оба инкремента до вызова первого print.

Что-то я плохо понимаю. Допустим, поведение четко не определено; но какие могут быть вообще варианты?

1. Вызвать оператор ++ 10 раз подряд, а все вызовы произвести по очереди с первоначальным значением a?

2. Интуитивно ожидаемый (v1 = a++; r1 = cout.print(val); v1 = a++; r2 = r1.print(val); etc.) ?

3. Модифицировать 10 раз, запомнить все промежуточные результаты, и все вызовы произвести с этими результатами, подставленными в случайном порядке? Т.е. если int a = 0, то может быть cout.print(2).print(1).print(0).print(3); и a равное 4 ?

...ещё?
Re[2]: Теоретический вопрос про cout << a++ << a++;
От: Кодёнок  
Дата: 07.10.05 11:22
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Кстати, operator<< — это не член ostream, а внешняя функция.

К>operator<<( // второй
К> operator<<( // первый
К> cout,
К> a++
К> ),
К> a++
К>);

Хм... дошло

Меня смутил оператор точка и его ассоциативность. Тогда как на самом деле получается

C::print(C::print(C::print(cout, a++), a++), a++);

где C — typeof(cout).
Re[3]: Теоретический вопрос про cout << a++ << a++;
От: Глеб Алексеев  
Дата: 07.10.05 11:51
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>Что-то я плохо понимаю. Допустим, поведение четко не определено; но какие могут быть вообще варианты?

По идее, правильнее не писать программы, которые от этого будут зависеть.
Из интереса можно поэкпериментировать.
Подопытный кролик:
#include <iostream>

using std::cout;
using std::endl;

int main() {
  
  int a=0;
  cout << "preincrement: " << ++a << ++a << ++a << ++a << endl;

  a=0;
  cout << "postincrement: " << a++ << a++ << a++ << a++ << endl;
  
  return 0;
}


Вот результаты с компиляторами под рукой (лучше бы конечно с VC7.1 Professional, т.к. Standard не оптимизирует)
VC 7.1 Standard edition, Debug:
preincrement: 4444
postincrement: 3210

VC 7.1 Standard edition, Release:
preincrement: 4444
postincrement: 0000

gcc 3.4.4 (Cygwin), с оптимизацией и без
preincrement: 1234
postincrement: 0123
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Теоретический вопрос про cout << a++ << a++;
От: Alxndr Германия http://www.google.com/profiles/alexander.poluektov#buzz
Дата: 07.10.05 13:18
Оценка: +1
Здравствуйте, Глеб Алексеев, Вы писали:

ГА>Здравствуйте, Кодт, Вы писали:


К>>Кстати, operator<< — это не член ostream, а внешняя функция.

ГА>Для встроенных типов это именно член ostream.

Не для всех. Контрпример — char
Re[4]: Теоретический вопрос про cout << a++ << a++;
От: Chez Россия  
Дата: 10.10.05 10:04
Оценка: 1 (1) -2
Здравствуйте, Глеб Алексеев, Вы писали:

ГА>VC 7.1 Standard edition, Debug:
ГА>preincrement: 4444
ГА>postincrement: 3210

ГА>VC 7.1 Standard edition, Release:
ГА>preincrement: 4444
ГА>postincrement: 0000

ГА>gcc 3.4.4 (Cygwin), с оптимизацией и без
ГА>preincrement: 1234
ГА>postincrement: 0123
ГА>

GCC прав, VC — баг, однозначно!
С чего это вдруг "Компилятор вправе вычислить оба инкремента до вызова первого print"?
На основании чего он вправе это делать? Есть таблица "Operator Associativity and Precedence" в соотв. с которой VC — бажит.

Выражение
cout << "preincrement: " << ++a << ++a << ++a << ++a << endl;
// это
((((((cout << "preincrement: ") << ++a) << ++a) << ++a) << ++a) << endl);
// левый операнд в операторе << вычисляется раньше правого, значит это:
cout << "preincrement: ";
cout << ++a;
cout << ++a;
cout << ++a;
cout << ++a;
cout << endl;
// или
cout << "preincrement: ";
cout << a; a = a + 1;
cout << a; a = a + 1;
cout << a; a = a + 1; 
cout << a; a = a + 1; // прошу не цепляться за a = a + 1, это не "буквально"
cout << endl;

какие могут быть разногласия?

Chez, ICQ#161095094

Posted via:RSDN@Home;version:1.1.3;muzikstamp:silent

Re: Теоретический вопрос про cout << a++ << a++;
От: Chez Россия  
Дата: 10.10.05 10:04
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>// 1. В поиске я нашёл, что это UB.

Кё>cout << a++ << a++;
Это не UB по стандарту, а код который может вызвать UB на глючных компиляторах.

Chez, ICQ#161095094

Posted via:RSDN@Home;version:1.1.3;muzikstamp:silent

Re: Теоретический вопрос про cout << a++ << a++;
От: Лазар Бешкенадзе СССР  
Дата: 10.10.05 13:54
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>
Кё>// 1. В поиске я нашёл, что это UB.
Кё>cout << a++ << a++;

Кё>// 2. А так?
Кё>cout.print(a++).print(a++);

Кё>// 3. А вот так?
Кё>cout.operator <<(a++).operator <<(a++);
Кё>


Кё>Пусть cout — класс, имеющий оператор << и функцию print, которые реализованы абсолютно одинаково, и возвращают ссылку на this.


Здесь UB надо понимать unspecified.
Ассоциативность говорит о том кто к кому липнет, то есть
о расстановке скобок:

(cout << (a++)) << (a++);

Так как оператор << переопределен, то для него как и для функции
есть точки следования. Значит это не undefined. С другой стороны
последовательность вычисления операндов в правом операторе не
специфицирована. Если сначала будет вычеслен a++, то для
начального значения a = 0, получим

10

Если сначала вычисляется левый оператор <<, получим

01

Верь мне,
Лазар (MCSD)
Re[2]: Теоретический вопрос про cout << a++ << a++;
От: Аноним  
Дата: 10.10.05 14:14
Оценка:
ЛБ>Так как оператор << переопределен, то для него как и для функции
ЛБ>есть точки следования.

Точка следования есть после вычисления аргументов. Но никто не гарантирует, что если есть несколько вызовов функции в одном выражении, то сначала вычислятся аргументы только одной функции и идет точка следования, и только потом вычилсяются аргументы другой функции.
Re[5]: Теоретический вопрос про cout << a++ << a++;
От: Кодт Россия  
Дата: 10.10.05 14:35
Оценка:
Здравствуйте, Chez, Вы писали:

C>GCC прав, VC — баг, однозначно!

C>С чего это вдруг "Компилятор вправе вычислить оба инкремента до вызова первого print"?
C>На основании чего он вправе это делать? Есть таблица "Operator Associativity and Precedence" в соотв. с которой VC — бажит.

Это глубочайшее заблуждение — полагать, что ассоциативность и предшествование здесь играют роль.
Данная таблица описывает свойства синтаксиса и определяет правила расстановки скобок. Не более того.
VC не бажит в этой области.

Вот рассмотрим пример без UB. (a = b = x / y / z / t)
Согласно правилам ассоциативности, скобки будут расстановлены так:
    дерево выражения
      |
+-+----------+
| |  +-+-------------+
| |  | |         +------+-+
| |  | |      +----+-+  | |
| |  | |    +-+-+  | |  | |
| |  | |    | | |  | |  | |
a = (b = (((x / y) / z) / t))
    :    :       :    : 
    :    :       деление левоассоциативно
    :    приоритет деления выше, чем присваивания
    присваивание правоассоциативно


А теперь обоснуй, в каком порядке должны вычисляться подвыражения x,y,z,t для того, чтобы посчитать (x/y), (x/y)/z, ((x/y)/z)/t) и выполнить присваивание?
Очевидно, в произвольном!

Именно это и значит, что если эти подвыражения содержат внутри себя точки следования (например, это вызовы функций), то их побочные эффекты следуют в произвольном порядке и дают unspecified behavior; а если точек следования нет (например, это автоинкремент), то их взаимное влияние даёт undefined behavior.

Почему же так жёстко? Почему нельзя заявить, что любая модификация переменной (будь то присваивание или автоинкремент) создаёт точку следования (и тем самым, получить всего лишь unspecified behavior — что, в общем-то, тоже не подарок)? Ответ: из соображений оптимизации.
Об этом уже много сказано, рекомендую поискать по форуму.
Перекуём баги на фичи!
Re[3]: Теоретический вопрос про cout << a++ << a++;
От: Лазар Бешкенадзе СССР  
Дата: 10.10.05 14:55
Оценка:
Здравствуйте, Аноним, Вы писали:

ЛБ>>Так как оператор << переопределен, то для него как и для функции

ЛБ>>есть точки следования.

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


5.2.2/1
... A function call is a postfix expression followed by parentheses
containing a possibly empty, comma separated list of expressions which
constitute the arguments to the function. ...

5.2.2/5
The order of evaluation of arguments is unspecified.
...
The order of evaluation of the postfix expression and
the argument expression list is unspecified.

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

Лазар
Re[4]: Теоретический вопрос про cout << a++ << a++;
От: Кодёнок  
Дата: 10.10.05 17:12
Оценка:
Здравствуйте, Лазар Бешкенадзе, Вы писали:

ЛБ>но предполагать, что начав вычислять одно выражение, компилятор

ЛБ>вдруг остановится и повычисляет "немоножечко" другого выражения,
ЛБ>это слишком.

Ну мало ли какие процессоры могут быть созданы Допустим несколько конвееров, и в каждом можно модифицировать ячейки памяти. А тип данных, который инкрементится, 64..1024-битный и за одну атомарную операцию точно не выполняется. А существовать такие процессоры могут исключительно в рассчете, что писать будут для умных компиляторов, которые не сгенерируют кода с неопределенным поведением для программы без оного. В этом случае оптимизатор имеет право расставить код вычисления `a++` так, что он будет выполняться в двух конвеерах.
Re[5]: Теоретический вопрос про cout << a++ << a++;
От: Лазар Бешкенадзе СССР  
Дата: 10.10.05 17:56
Оценка:
Здравствуйте, Кодёнок, Вы писали:

Кё>Здравствуйте, Лазар Бешкенадзе, Вы писали:


ЛБ>>но предполагать, что начав вычислять одно выражение, компилятор

ЛБ>>вдруг остановится и повычисляет "немоножечко" другого выражения,
ЛБ>>это слишком.

Кё>Ну мало ли какие процессоры могут быть созданы Допустим несколько конвееров, и в каждом можно модифицировать ячейки памяти. А тип данных, который инкрементится, 64..1024-битный и за одну атомарную операцию точно не выполняется. А существовать такие процессоры могут исключительно в рассчете, что писать будут для умных компиляторов, которые не сгенерируют кода с неопределенным поведением для программы без оного. В этом случае оптимизатор имеет право расставить код вычисления `a++` так, что он будет выполняться в двух конвеерах.


Был неправ. Все-таки на работе надо делать работу.
Sequence point стоит после вычисления _всех_ аргументов, но до
выполнения тела функции.
Как там было?

(cout << (a++)) << (a++)

Например имеем вычисленный но без завершенных побочных эффектов
правый a++. Начинаем вычислять операнды для левого << и на этом
самом sequence point для левого << получаем незавершенные side
effects от обоих инкрементов.

Еще раз пардон.
Лазар
Re[6]: Теоретический вопрос про cout << a++ << a++;
От: Chez Россия  
Дата: 11.10.05 06:56
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Это глубочайшее заблуждение — полагать, что ассоциативность и предшествование здесь играют роль.

К>Данная таблица описывает свойства синтаксиса и определяет правила расстановки скобок. Не более того.
Оч. странно. Ведь когда внутри скобки есть 2 операнда, всегда можно сказать, какой будет вычислен первым, а какой — вторым?!!
К>VC не бажит в этой области.

К>Вот рассмотрим пример без UB. (a = b = x / y / z / t)

К>Согласно правилам ассоциативности, скобки будут расстановлены так:
К> kromsated

К>А теперь обоснуй, в каком порядке должны вычисляться подвыражения x,y,z,t для того, чтобы посчитать (x/y), (x/y)/z, ((x/y)/z)/t) и выполнить присваивание?

К>Очевидно, в произвольном!
Для меня это абсолютно не очевидно!
Вычисление подвыражений должно происходить как обычно — в направлении ассоциативности, в порядке приоритета операций
0) a = (b = (((x / y) / z) / t))
0.0) (b = (((x / y) / z) / t))
0.0.0) (((x / y) / z) / t)
0.0.0.0) ((x / y) / z)
0.0.0.0.0) (x / y)
0.0.0.0.0.0) x (1)
0.0.0.0.0.1) y (2)
0.0.0.0.1) z (3)
0.0.0.1) t (4)
0.0.1) b (5)
0.1) a (6)
Для меня сложно понять, как умные люди из комитета могли в этом месте придумать UB!
Если здесь действительно UB, то для меня это неприятная новость, и камень в огород плюсов.

К>Почему же так жёстко? Почему нельзя заявить, что любая модификация переменной (будь то присваивание или автоинкремент) создаёт точку следования (и тем самым, получить всего лишь unspecified behavior — что, в общем-то, тоже не подарок)? Ответ: из соображений оптимизации.

Далеко бы я послал эти соображения, если б меня спросили...
К>Об этом уже много сказано, рекомендую поискать по форуму.
Ок. Поищу. Спасибо.

Chez, ICQ#161095094

Posted via:RSDN@Home;version:1.1.3;muzikstamp:silent

Re[7]: Теоретический вопрос про cout << a++ << a++;
От: jazzer Россия Skype: enerjazzer
Дата: 11.10.05 08:21
Оценка:
Здравствуйте, Chez, Вы писали:

C>Здравствуйте, Кодт, Вы писали:


К>>Это глубочайшее заблуждение — полагать, что ассоциативность и предшествование здесь играют роль.

К>>Данная таблица описывает свойства синтаксиса и определяет правила расстановки скобок. Не более того.

C>Оч. странно. Ведь когда внутри скобки есть 2 операнда, всегда можно сказать, какой будет вычислен первым, а какой — вторым?!!


нет. предствь, что у тебя 12 процессоров на машине установлено.

К>>Вот рассмотрим пример без UB. (a = b = x / y / z / t)

К>>Согласно правилам ассоциативности, скобки будут расстановлены так:
К>> kromsated

К>>А теперь обоснуй, в каком порядке должны вычисляться подвыражения x,y,z,t для того, чтобы посчитать (x/y), (x/y)/z, ((x/y)/z)/t) и выполнить присваивание?

К>>Очевидно, в произвольном!
C>Для меня это абсолютно не очевидно!
C>Вычисление подвыражений должно происходить как обычно — в направлении ассоциативности, в порядке приоритета операций
C>Для меня сложно понять, как умные люди из комитета могли в этом месте придумать UB!
C>Если здесь действительно UB, то для меня это неприятная новость, и камень в огород плюсов.

Они не придумали UB.
Они предоставили возможности для оптимизации.
Например, без этой возможности нельзя было бы вычисление операндов произвести параллельно на разных процессорах.

А ты просто должен знать, в каких случаях может возникнуть UB.
И если тебе надо нечто вычислить обязательно в определенном порядке, без возможности распараллеливания, используй синтаксис с явными точками следования (operator,).

К>>Почему же так жёстко? Почему нельзя заявить, что любая модификация переменной (будь то присваивание или автоинкремент) создаёт точку следования (и тем самым, получить всего лишь unspecified behavior — что, в общем-то, тоже не подарок)? Ответ: из соображений оптимизации.

C>Далеко бы я послал эти соображения, если б меня спросили...

см. выше.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: Теоретический вопрос про cout << a++ << a++;
От: Кодт Россия  
Дата: 11.10.05 08:23
Оценка: 4 (1)
Здравствуйте, Chez, Вы писали:

К>>Это глубочайшее заблуждение — полагать, что ассоциативность и предшествование здесь играют роль.

К>>Данная таблица описывает свойства синтаксиса и определяет правила расстановки скобок. Не более того.
C>Оч. странно. Ведь когда внутри скобки есть 2 операнда, всегда можно сказать, какой будет вычислен первым, а какой — вторым?!!

Кому можно сказать?
Ну хорошо, давай посмотрим на такой пример
printf("%d %d %d", x(), y(), z());

Внутри скобки 4 операнда — одна константа и три вычислимых.
В соответствии с конвенцией вызова cdecl или stdcall (здесь cdecl) операнды должны располагаться на стеке в порядке
(вершина стека) (первый) (второй) (третий) (четвёртый) .....
                 строка     x        y          z

Или, отвлекаясь от "лево-правого" вида, чтобы он не вгонял нас в рамки,

(вершина)
(первый)    строка
(второй)    x
(третий)    y
(четвёртый) z
.....

Как удобнее их запихивать туда?
— вычислить x и запомнить, вычислить y и запомнить, вычислить z и положить в стек, вспомнить y и положить, вспомнить x и положить
— зарезервировать место для всех сразу, вычислить-и-положить в произвольном порядке
— вычислить z и положить, вычислить y и положить, вычислить x и положить

Для компилятора без оптимизации — наиболее эффективен последний способ.
Для человека кажется естественным первый.
С оптимизацией может быть и второй. Например,
printf("%g %g %g", cos(x)*sin(y), sqrt(x*y), cos(x)*sin(y));

cos, sin — это встроенные (intrinsic) функции, компилятор заменит их на команды FPU. Очевидно, что результат вычисления можно сразу положить в две ячейки стека, не отвлекаясь на вычисление sqrt(x*y).

Кстати говоря, конвенция pascal располагает аргументы в стеке "наоборот", так, чтобы естественный порядок вычислений совпал с порядком чтения.

К>>Вот рассмотрим пример без UB. (a = b = x / y / z / t)

К>>Согласно правилам ассоциативности, скобки будут расстановлены так:
К>> kromsated
К>>А теперь обоснуй, в каком порядке должны вычисляться подвыражения x,y,z,t для того, чтобы посчитать (x/y), (x/y)/z, ((x/y)/z)/t) и выполнить присваивание?
К>>Очевидно, в произвольном!
C>Для меня это абсолютно не очевидно!
C>Вычисление подвыражений должно происходить как обычно — в направлении ассоциативности, в порядке приоритета операций
C>0) a = (b = (((x / y) / z) / t))
C>0.0) (b = (((x / y) / z) / t))
C>0.0.0) (((x / y) / z) / t)
C>0.0.0.0) ((x / y) / z)
C>0.0.0.0.0) (x / y)
C>0.0.0.0.0.0) x (1)
C>0.0.0.0.0.1) y (2)
C>0.0.0.0.1) z (3)
C>0.0.0.1) t (4)
C>0.0.1) b (5)
C>0.1) a (6)
C>Для меня сложно понять, как умные люди из комитета могли в этом месте придумать UB!
C>Если здесь действительно UB, то для меня это неприятная новость, и камень в огород плюсов.

Здесь не undefined, а unspecified. Если тебе пофиг, в каком порядке следуют побочные эффекты, то проблемы нет. Если не пофиг — проблемы будут.

А насчёт камней в огороды... Не бегай в валенках по конькобежной дорожке, так и не упадёшь.
Перекуём баги на фичи!
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.