Re[10]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 14.06.17 23:41
Оценка:
Здравствуйте, N. I., Вы писали:

C>>Точки следования однозначно есть при вызове функции, в том числе конструктора std::shared_ptr.

C>>Про то, что их нет в последних стандартах я и так знаю, ограничимся C++03 для простоты.
NI>C++03 допускал возможность частично или полностью вычислять аргумент функции между вычислениями двух частей другого её аргумента так же, как и C++11 вместе с C++14. Наличие точки следования на входе в тело конструктора std::shared_ptr никак не противоречит возможности размещения оной после вычисления другого аргумента функции.
Противоречит из-за самого определения точки следования.
Sapienti sat!
Re[10]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 14.06.17 23:42
Оценка:
Здравствуйте, dead0k, Вы писали:

C>>Про то, что их нет в последних стандартах я и так знаю, ограничимся C++03 для простоты.

D>sequence point-ы регулируют сайд-еффекты, а не вычисления.
А разница в чём в данном случае? Бросок исключения — это сторонний эффект. Т.е. между new и shared_ptr::ctor его быть не может.
Sapienti sat!
Re[11]: Почему в этом коде течет память
От: jazzer Россия Skype: enerjazzer
Дата: 15.06.17 00:20
Оценка:
Здравствуйте, Cyberax, Вы писали:

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


C>>>Про то, что их нет в последних стандартах я и так знаю, ограничимся C++03 для простоты.

D>>sequence point-ы регулируют сайд-еффекты, а не вычисления.
C>А разница в чём в данном случае? Бросок исключения — это сторонний эффект. Т.е. между new и shared_ptr::ctor его быть не может.

Вот это ты с чего взял?
Точка следования (перечитай определение) лишь означает, что все эффекты от new должны сработать до того, как начнутся эффекты от ctor. Всё.
Очевидно, вставка между ними эффектов от f() этого свойства не изменяет.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Отредактировано 15.06.2017 0:26 jazzer . Предыдущая версия .
Re[12]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 15.06.17 05:07
Оценка:
Здравствуйте, jazzer, Вы писали:

C>>А разница в чём в данном случае? Бросок исключения — это сторонний эффект. Т.е. между new и shared_ptr::ctor его быть не может.

J>Вот это ты с чего взял?
J>Точка следования (перечитай определение) лишь означает, что все эффекты от new должны сработать до того, как начнутся эффекты от ctor. Всё.
Каким образом f() будет переупорядочена через точку следования? Это барьер для всех вычислений.

If a sequence point is present between the subexpressions E1 and E2, then both value computation and side effects of E1 are sequenced-before every value computation and side effect of E2


Я не вижу каким образом побочные эффекты f() могут быть перенесены за аж две точки следования.
Sapienti sat!
Re[13]: Почему в этом коде течет память
От: jazzer Россия Skype: enerjazzer
Дата: 15.06.17 06:42
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>Каким образом f() будет переупорядочена через точку следования? Это барьер для всех вычислений.


Вот в это твоя ошибка и сидит.
Нет, точка следования — это не глобальный барьер, это относительное упорядочение.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Отредактировано 15.06.2017 6:46 jazzer . Предыдущая версия .
Re[14]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 15.06.17 08:26
Оценка:
Здравствуйте, jazzer, Вы писали:

C>>Каким образом f() будет переупорядочена через точку следования? Это барьер для всех вычислений.

J>Вот в это твоя ошибка и сидит.
J>Нет, точка следования — это не глобальный барьер, это относительное упорядочение.
В Стандарте вроде бы ясно написано:

Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O
function, or calling a function that does any of those operations are all side effects, which are changes in the
state of the execution environment. Evaluation of an expression might produce side effects. At certain
specified points in the execution sequence called sequence points, all side effects of previous evaluations
shall be complete and no side effects of subsequent evaluations shall have taken place.


Я не знаю как это трактовать иначе, кроме как глобальный барьер. Тем более, что на практике компиляторы так и делают

Про более новые С++ не разбирался.
Sapienti sat!
Re[15]: Почему в этом коде течет память
От: jazzer Россия Skype: enerjazzer
Дата: 15.06.17 08:34
Оценка:
Здравствуйте, Cyberax, Вы писали:

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


C>>>Каким образом f() будет переупорядочена через точку следования? Это барьер для всех вычислений.

J>>Вот в это твоя ошибка и сидит.
J>>Нет, точка следования — это не глобальный барьер, это относительное упорядочение.
C>В Стандарте вроде бы ясно написано:
C>

C>Accessing an object designated by a volatile lvalue (3.10), modifying an object, calling a library I/O
C>function, or calling a function that does any of those operations are all side effects, which are changes in the
C>state of the execution environment. Evaluation of an expression might produce side effects. At certain
C>specified points in the execution sequence called sequence points, all side effects of previous evaluations
C>shall be complete and no side effects of subsequent evaluations shall have taken place.


C>Я не знаю как это трактовать иначе, кроме как глобальный барьер. Тем более, что на практике компиляторы так и делают


Тут ключевое — previous evaluations. В случае вычисления аргументов функции (и их подвыражений) их порядок полностью неопределен, поэтому в список предыдущих может попасть все, что угодно.

C>Про более новые С++ не разбирался.


Вот в новом С++17 как раз этой проблемы нет: там по-прежнему не определен порядок вычисления аргументов, но подвыражения перемешивать уже нельзя. А раньше было можно, о чем весь разговор и идёт, а ты, фактически, утверждаешь, что ничего менять в С++17 не надо было, все и так работало. Ты неправ — не работало.
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[13]: Почему в этом коде течет память
От: dead0k  
Дата: 15.06.17 09:14
Оценка:
Здравствуйте, Cyberax, Вы писали:

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


C>Я не вижу каким образом побочные эффекты f() могут быть перенесены за аж две точки следования.

Переносятся не применение сайд-эффектов, а целиком вычисление f() со всем багажом.
ctor(new T)
означает, что:
— сначала выозвется new T и применятся все его сайдэффекты
— когда-нибудь потом вызовется ctor (после того, как все сайдэффекты предыдущих вычислений будут исполнены).
Между этими двумя действиями компилятор волен петь и плясасть вместе со всем индийским народом.
Re[13]: Почему в этом коде течет память
От: N. I.  
Дата: 15.06.17 10:35
Оценка:
Cyberax:

C>

If a sequence point is present between the subexpressions E1 and E2, then both value computation and side effects of E1 are sequenced-before every value computation and side effect of E2


Во-первых, это неправильное утверждение с неофициального источника. Его неправильность очевидна хотя бы из-за симметричности формулировки "a sequence point is present between the subexpressions E1 and E2" по отношению к E1 и E2: если точка следования присутствует между E1 и Е2, то она присутствует также между E2 и E1, и тогда мы приходим к выводу, что одновременно верны два взаимоисключающих утверждения:

1) both value computation and side effects of E1 are sequenced-before every value computation and side effect of E2;
2) both value computation and side effects of E2 are sequenced-before every value computation and side effect of E1.

Во-вторых, даже если поправить его на нечто вроде

"If a sequence point is present between subexpressions E1 and E2 of a full-expression E, then either every value computation and side effect associated with E1 is sequenced before every value computation and side effect associated with E2 or every value computation and side effect associated with E2 is sequenced before every value computation and side effect associated with E1; for the purposes of this rule, destruction of any temporary objects created in the immediate context of E1 or E2 is not considered to be associated with these subexpressions",

то в данном случае применить это правило к подвыражениям std::shared_ptr<T>(new T) и f(), сопоставив их E1 и E2 соответственно, мы не сможем, потому что ничто в стандарте C++03 не указывает на то, что эти два подвыражения полностью отделены друг от друга некоей точкой следования.

Точка следования отделяет собой некое множество "previous evaluations" от некоего другого множества "subsequent evaluations". Для некоторых случаев C++03 уточняет, что входит в эти множества — например, в случае со встроенным оператором-запятой:

All side effects (1.9) of the left expression, except for the destruction of temporaries (12.2), are performed before the evaluation of the right expression.


Если же множество вычислений "previous evaluations" или "subsequent evaluations" явным образом не указано, то максимум, что мы можем рассматривать в качестве одного из таких множеств — это одно-единственное элементарное вычисление. Нельзя просто так взять от балды любую последовательность вычислений и сказать, что по отношению к некоей точке следования она целиком относится к "previous evaluations" или "subsequent evaluations".
Re[16]: Почему в этом коде течет память
От: jazzer Россия Skype: enerjazzer
Дата: 15.06.17 11:35
Оценка: 9 (2)
Здравствуйте, jazzer, Вы писали:

J>Тут ключевое — previous evaluations. В случае вычисления аргументов функции (и их подвыражений) их порядок полностью неопределен, поэтому в список предыдущих может попасть все, что угодно.


Поясню на примере (до этого писал с сотового, неудобно)
В коде
processWidget(std::shared_ptr<T>(new T), f());

есть ровно две точки следования: 1) между new T и конструктором std::shared_ptr<T>, и 2) между всеми аргументами processWidget и телом processWidget.
Это означает, что код компилятором может быть представлен следующим образом:
auto tmp1 = new T;
auto tmp2 = f();
auto tmp3 = std::shared_ptr<T>(tmp1);
processWidget(tmp3, tmp2);

точки следования:
1) сайд-эффекты от вычисления tmp3 не могут начаться, пока не отработают сайд-эффекты от tmp1.
2) сайд-эффекты processWidget не могут начаться, пока не отработают сайд-эффекты от tmp3 и tmp2 (в любом относительном порядке).

То есть возможны варианты:
1)tmp1, tmp2, tmp3, processWidget
2)tmp1, tmp3, tmp2, processWidget
3)tmp2, tmp1, tmp3, processWidget

C>>Про более новые С++ не разбирался.


J>Вот в новом С++17 как раз этой проблемы нет: там по-прежнему не определен порядок вычисления аргументов, но подвыражения перемешивать уже нельзя. А раньше было можно, о чем весь разговор и идёт, а ты, фактически, утверждаешь, что ничего менять в С++17 не надо было, все и так работало. Ты неправ — не работало.


С++17 к тому, что было сказано выше, добавляет еще одно условие: Если мы взялись вычислять какой-то аргумент функции, то мы не имеем права отложить его на полпути и повычислять другой, а потом вернуться к этому.
То есть tmp3 должно идти строго за tmp1 и tmp2 между ними вставлять нельзя. Таким образом, вариант номер один оказывается запрещен, разрешены только 2 и 3.
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[13]: Почему в этом коде течет память
От: Erop Россия  
Дата: 15.06.17 11:53
Оценка:
Здравствуйте, Cyberax, Вы писали:


C>Я не вижу каким образом побочные эффекты f() могут быть перенесены за аж две точки следования.


new A( f() )
можно переписать как
new( operator new( sizeof A ) ) A( f() )

то есть, фактически, подвыражения operator new( sizeof A ) и f() -- это аргументы вызова конструктора по месту в памяти (new( void* ) A( decltype( f() ) ))
Конечно это не совсем обычная функция с двумя аргументами, тем не менее это она. В общем случае порядок вычисления аргументов не определён, есть какое-то уточнение в стандарте на этот конкретный случай?
Если да, то может ты его просто укажешь?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Отредактировано 15.06.2017 12:23 Erop . Предыдущая версия .
Re[17]: Почему в этом коде течет память
От: N. I.  
Дата: 15.06.17 12:42
Оценка:
jazzer:

J>есть ровно две точки следования: 1) между new T и конструктором std::shared_ptr<T>, и 2) между всеми аргументами processWidget и телом processWidget.

J>Это означает, что код компилятором может быть представлен следующим образом:
J>
auto tmp1 = new T;
auto tmp2 = f();
auto tmp3 = std::shared_ptr<T>(tmp1);
processWidget(tmp3, tmp2);

Кстати, new-выражение тоже может вычисляться по частям. Если new T инициализирует объект конструктором, то может получиться что-то вроде

// step 1
T *tmp1 = (T *)operator new(sizeof T);

// step 2
auto &&tmp2 = f();

// step 3
try
{
    __default_construct<T>(tmp1);
}
catch (...)
{
    operator delete(tmp1);
    throw;
}

// step 4
auto tmp3 = std::shared_ptr<T>(tmp1);

// step 5
processWidget(tmp3, tmp2);

В таком случае если f() бросает исключение, то до конструирования объекта типа T мы так и не добираемся, но память всё равно течёт, потому что исключение возникло снаружи new-expression, что снимает с этого new-expression обязанность делать откат.
Re[14]: Почему в этом коде течет память
От: N. I.  
Дата: 15.06.17 12:43
Оценка:
N. I.:

NI>Во-первых, это неправильное утверждение с неофициального источника. Его неправильность очевидна хотя бы из-за симметричности формулировки "a sequence point is present between the subexpressions E1 and E2" по отношению к E1 и E2: если точка следования присутствует между E1 и Е2, то она присутствует также между E2 и E1, и тогда мы приходим к выводу, что одновременно верны два взаимоисключающих утверждения:


Оказывается, сия пакость с асимметричным "between" берёт начало в C11:

The presence of a sequence point between the evaluation of expressions A and B implies that every value computation and side effect associated with A is sequenced before every value computation and side effect associated with B.


Видать, сишные стандартизаторы временами тоже имеют проблемы с изложением правил человеческим языком, как и плюсовые.
Re[16]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 15.06.17 18:44
Оценка:
Здравствуйте, jazzer, Вы писали:

C>>Я не знаю как это трактовать иначе, кроме как глобальный барьер. Тем более, что на практике компиляторы так и делают

J>Тут ключевое — previous evaluations. В случае вычисления аргументов функции (и их подвыражений) их порядок полностью неопределен, поэтому в список предыдущих может попасть все, что угодно.
Да, понятие "предыдущих"/"следующих" не определено чётко. Но если так читать, то вообще в С++ нет никакого порядка вычислений. Можно хоть в обратном порядке выполнять. Для конкретно аргументов функции есть заметка о возможности вычислять их в произвольном порядке, но оно не должно влиять на точки следования.

И повторю опять — на практике ВСЕ основные С++ компиляторы следуют моему чтению. Т.е. точка следования является барьером для побочных эффектов.

C>>Про более новые С++ не разбирался.

J>Вот в новом С++17 как раз этой проблемы нет: там по-прежнему не определен порядок вычисления аргументов, но подвыражения перемешивать уже нельзя. А раньше было можно, о чем весь разговор и идёт, а ты, фактически, утверждаешь, что ничего менять в С++17 не надо было, все и так работало. Ты неправ — не работало.
Я не вижу как это возможно в старом С++.
Sapienti sat!
Re[17]: Почему в этом коде течет память
От: N. I.  
Дата: 15.06.17 20:53
Оценка: +2
Cyberax:

C>Да, понятие "предыдущих"/"следующих" не определено чётко. Но если так читать, то вообще в С++ нет никакого порядка вычислений. Можно хоть в обратном порядке выполнять.


Ну, здрасьте. Для некоторых конструкций порядок, по-моему, достаточно ясно определён, хотя в C++03 это зачастую делается через одно место (путём неформального описания вперемешку с кривыми формальными правилами). Например, вот что говорится о statement-ах:

C++03 — 6/1:

Except as indicated, statements are executed in sequence.


Тут стандартизаторы могли бы выразиться более формальным языком, использовав "in lexical order" вместо "in sequence". Но отсюда и так понятно, что если у нас есть два соседних expression-statements S1 и S2, где S2 лексически следует за S1, то первым должно вычислиться S1, а вслед за ним уже S2 (если какой-то иной расклад явным образом не оговорен). При этом если S1 содержит какое-то full-expression E1, то в конце E1 есть точка следования (C++03 — 1.9/16), которая отделит собой вычисления, связанные с E1, от вычислений, связанных с S2. Пожалуйста, вот тебе деление на предыдущие и следующие вычисления.

C>И повторю опять — на практике ВСЕ основные С++ компиляторы следуют моему чтению.


Каким образом ты это выяснял? Чтением мануалов, изучением исходников компиляторов или методом тыка на конкретных примерах?

C>Т.е. точка следования является барьером для побочных эффектов.


С этим никто не спорит. Вопрос в том, барьером каких именно вычислений она является.

C>Я не вижу как это возможно в старом С++.


Ну, мы старались как могли Можешь спросить про точки следования на SO или форуме isocpp — возможно, там тебе то же самое более доходчиво объяснят.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.