Почему в этом коде течет память
От: techgl  
Дата: 08.06.17 11:50
Оценка:
Здравствуйте, pkl, Вы писали:

pkl>Был на собеседе примерно год назад.


pkl>4) Вопросы были такие: зачем std::make_shared, если можно без него. Чем shared_ptr от weak_ptr отличается. Что-то там про исключения, типа можно ли кидать одно не перехватив ещё другое и если нет, то как сделать чтобы было можно. Отчего возникает pure virtual call. Был вопрос: "почему в этом коде течёт память: std::shared_ptr<T> t(new T( f() ) );" f() -- память не выделяет, возвращает int, может кидануть эксцепшн. Видимо она там никак не течёт, вопрос был психотронный — как я ему докажу обратное.

Например, почитать книгу Effective Modern C++ и Item 21 в ней. Тогда не надо будет ничего доказывать.


10.06.17 14:01: Ветка выделена из темы JetBrains &mdash; Был на собеседе
Автор: Gattaka
Дата: 19.05.17
— Берсерк
12.06.17 20:48: Ветка выделена из темы Особенности std::shared_ptr — AndrewVK
Re: Почему в этом коде течет память
От: · Великобритания  
Дата: 08.06.17 12:18
Оценка:
Здравствуйте, techgl, Вы писали:

pkl>>4) Вопросы были такие: зачем std::make_shared, если можно без него. Чем shared_ptr от weak_ptr отличается. Что-то там про исключения, типа можно ли кидать одно не перехватив ещё другое и если нет, то как сделать чтобы было можно. Отчего возникает pure virtual call. Был вопрос: "почему в этом коде течёт память: std::shared_ptr<T> t(new T( f() ) );" f() -- память не выделяет, возвращает int, может кидануть эксцепшн. Видимо она там никак не течёт, вопрос был психотронный — как я ему докажу обратное.

T>Например, почитать книгу Effective Modern C++ и Item 21 в ней. Тогда не надо будет ничего доказывать.
В Item 21 код несколько другой:
processWidget(std::shared_ptr<T>(new T), f());

Тут выражение с new никак от вызова f не зависит и поэтому компилятору можно переставлять. А в примере pkl это аргумент конструктора, а значит нечего переставлять. Откуда там может быть утечка?!
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: Почему в этом коде течет память
От: _Artem_ Россия  
Дата: 08.06.17 14:53
Оценка:
Здравствуйте, ·, Вы писали:

·>В Item 21 код несколько другой:

·>
·>processWidget(std::shared_ptr<T>(new T), f());
·>

·>Тут выражение с new никак от вызова f не зависит и поэтому компилятору можно переставлять. А в примере pkl это аргумент конструктора, а значит нечего переставлять. Откуда там может быть утечка?!

Вообще, когда работает конструктор std::shared_ptr<T>(T *), то может генерироваться исключение std::bad_alloc, ведь идет выделение динамической памяти под счетчики (strong и weak). Таким образом указатель на объект не будет удален, по крайней мере до 11 или 14 стандарта. Отсюда и может быть утечка. В более поздних стандартах эту дыру подлатали, вызывается деструктор для T*.
Re[2]: Почему в этом коде течет память
От: StatujaLeha на правах ИМХО
Дата: 08.06.17 15:08
Оценка:
Здравствуйте, ·, Вы писали:

·>
·>processWidget(std::shared_ptr<T>(new T), f());
·>

·>Тут выражение с new никак от вызова f не зависит и поэтому компилятору можно переставлять. А в примере pkl это аргумент конструктора, а значит нечего переставлять. Откуда там может быть утечка?!

std::shared_ptr<T> t(); =>

auto p = new T(f());
std::shared_ptr<T> t(p); => new inline

p = memoryAlloc()
constructor T(p, f())
std::shared_ptr<T> t(p) =>

constructor T(memoryAlloc(), f()) //тут уже можно и менять порядок вычислений аргументов
std::shared_ptr<T> t(p)

может так?
Re[3]: Почему в этом коде течет память
От: · Великобритания  
Дата: 08.06.17 15:31
Оценка:
Здравствуйте, _Artem_, Вы писали:

_A_>·>В Item 21 код несколько другой:

_A_>·>
_A_>·>processWidget(std::shared_ptr<T>(new T), f());
_A_>·>

_A_>·>Тут выражение с new никак от вызова f не зависит и поэтому компилятору можно переставлять. А в примере pkl это аргумент конструктора, а значит нечего переставлять. Откуда там может быть утечка?!
_A_>Вообще, когда работает конструктор std::shared_ptr<T>(T *), то может генерироваться исключение std::bad_alloc, ведь идет выделение динамической памяти под счетчики (strong и weak).
_A_>Таким образом указатель на объект не будет удален, по крайней мере до 11 или 14 стандарта. Отсюда и может быть утечка. В более поздних стандартах эту дыру подлатали, вызывается деструктор для T*.
Тогда собственно даже вот такой код std::shared_ptr<T> t(new T) потенциально утечку. Причём тут тогда f()?
Как же тогда make_shared внутри устроет, что он избегает эту же проблему?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[3]: Почему в этом коде течет память
От: · Великобритания  
Дата: 08.06.17 15:36
Оценка:
Здравствуйте, StatujaLeha, Вы писали:

SL>·>
SL>·>processWidget(std::shared_ptr<T>(new T), f());
SL>·>

SL>·>Тут выражение с new никак от вызова f не зависит и поэтому компилятору можно переставлять. А в примере pkl это аргумент конструктора, а значит нечего переставлять. Откуда там может быть утечка?!

SL>std::shared_ptr<T> t(); =>


SL>auto p = new T(f());

SL>std::shared_ptr<T> t(p); => new inline

SL>p = memoryAlloc()

SL>constructor T(p, f())
SL>std::shared_ptr<T> t(p) =>

SL>constructor T(memoryAlloc(), f()) //тут уже можно и менять порядок вычислений аргументов

SL>std::shared_ptr<T> t(p)

SL>может так?

Смотрим имплементацию:
template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
  return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}

Т.е. то же самое изменение порядка вычисления аргументов возможно. В чём прикол?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[2]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 08.06.17 17:04
Оценка: -1 :)
Здравствуйте, ·, Вы писали:

·>В Item 21 код несколько другой:

·>
·>processWidget(std::shared_ptr<T>(new T), f());
·>

·>Тут выражение с new никак от вызова f не зависит и поэтому компилятору можно переставлять.
Не может. Запятая — это точка следования.
Sapienti sat!
Re[4]: Почему в этом коде течет память
От: _Artem_ Россия  
Дата: 08.06.17 17:17
Оценка:
Здравствуйте, ·, Вы писали:

·>Тогда собственно даже вот такой код std::shared_ptr<T> t(new T) потенциально утечку. Причём тут тогда f()?

·>Как же тогда make_shared внутри устроет, что он избегает эту же проблему?

Пардон, я что-то фигню спорол. В коде std::shared_ptr<T> t(new T) нет утечки памяти. Собственно в изначальном от JetBrains: std::shared_ptr<T> t(new T( f() ) ); тоже никакой утечки нет.
Re[3]: Почему в этом коде течет память
От: _Artem_ Россия  
Дата: 08.06.17 17:20
Оценка:
Здравствуйте, Cyberax, Вы писали:

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


C>·>В Item 21 код несколько другой:

C>·>
C>·>processWidget(std::shared_ptr<T>(new T), f());
C>·>

C>·>Тут выражение с new никак от вызова f не зависит и поэтому компилятору можно переставлять.
C>Не может. Запятая — это точка следования.

А вот и может. Компилятор может сначала сделать new T, потом вызвать функцию f(), получить exception и уйти на обработку исключения. Собственно тогда деструктор ранее созданного объекта не будет вызван.
Re[4]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 08.06.17 18:11
Оценка: -1
Здравствуйте, _Artem_, Вы писали:

C>>Не может. Запятая — это точка следования.

_A_>А вот и может.
Нет. Пункт Стандарта 1.9/17, первая запятая вносит точку следования.

Точки следования:
Evaluation of an expression might produce side effects. At certain specified points in the execution se-
quence 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[5]: Почему в этом коде течет память
От: · Великобритания  
Дата: 08.06.17 20:40
Оценка:
Здравствуйте, Cyberax, Вы писали:

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


C>>>Не может. Запятая — это точка следования.

_A_>>А вот и может.
C>Нет. Пункт Стандарта 1.9/17, первая запятая вносит точку следования.

C>Точки следования:

C>
C>Evaluation of an expression might produce side effects. At certain specified points in the execution se-
C>quence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
C>

О чём же тогда Item 21?

processWidget(std::shared_ptr<Widget>(new Widget), computePriority()); // potential resource leak!
...
Compilers are not required to generate code that executes them in this order. “new Widget” must be executed before the std::shared_ptr constructor may be called, because the result of that new is used as an argument to that constructor, but compute Priority may be executed before those calls, after them, or, crucially, between them. That is, compilers may emit code to execute the operations in this order:
1. Perform “new Widget”.
2. Execute computePriority.
3. Run std::shared_ptr constructor.
If such code is generated and, at runtime, computePriority produces an exception, the dynamically allocated Widget from Step 1 will be leaked, because it will never be stored in the std::shared_ptr that’s supposed to start managing it in Step 3.

но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[6]: Почему в этом коде течет память
От: opt1k США  
Дата: 08.06.17 22:29
Оценка:
Скам Вы хоть знаете, что такое скам? В моей компании разработчики работаю как субподрядчики и пилят корп. софт. Софтом пользуются десятки тысяч людей. Периодически наши разработчики меняют бизнес-логику так, что половина клиентов отваливается. Чтобы вернуть всё взад, они берут по 400$/Час.

Вот это я понимаю скам. А багфикс за деньги это цветочки.
Коплю на ланцер
Re[6]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 08.06.17 22:51
Оценка:
Здравствуйте, ·, Вы писали:

·>О чём же тогда Item 21?

А это просто ошибка. Я даже не представляю как можно получить неправильное поведение в данном примере, так как вызовы функций тоже создают точки следования.
Sapienti sat!
Отредактировано 08.06.2017 22:52 Cyberax . Предыдущая версия .
Re[5]: Почему в этом коде течет память
От: _Artem_ Россия  
Дата: 09.06.17 01:42
Оценка: +1
Здравствуйте, Cyberax, Вы писали:

C>Нет. Пункт Стандарта 1.9/17, первая запятая вносит точку следования.


C>Точки следования:

C>
C>Evaluation of an expression might produce side effects. At certain specified points in the execution se-
C>quence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.
C>


В том то и дело, что запятая не вносит точку следования (если это, конечно, не отдельный "operator,", а разделитель аргументов функции). Единственная гарантия, это то, что все аргументы должны быть вычислены перед вызовом функции. Отсюда все сайд-эффекты с исключениями.
Re[6]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 09.06.17 05:40
Оценка:
Здравствуйте, _Artem_, Вы писали:

_A_>В том то и дело, что запятая не вносит точку следования (если это, конечно, не отдельный "operator,", а разделитель аргументов функции). Единственная гарантия, это то, что все аргументы должны быть вычислены перед вызовом функции. Отсюда все сайд-эффекты с исключениями.

Даже и в этом случае, точки следования вносят и вызовы функций или конструкторов. Т.е. порядок вычисления аргументов в "f(a, b)" будет неопределён, но вот "f(b(), c)" будет уже определён (1.9/17 в C++98).

Практически, это означает лишь, что можно получить ловушки лишь в чём-то типа "a(b++, b)".
Sapienti sat!
Re[7]: Почему в этом коде течет память
От: _Artem_ Россия  
Дата: 09.06.17 06:26
Оценка:
Здравствуйте, Cyberax, Вы писали:

C>Даже и в этом случае, точки следования вносят и вызовы функций или конструкторов. Т.е. порядок вычисления аргументов в "f(a, b)" будет неопределён, но вот "f(b(), c)" будет уже определён (1.9/17 в C++98).


C>Практически, это означает лишь, что можно получить ловушки лишь в чём-то типа "a(b++, b)".


http://alenacpp.blogspot.ru/2005/11/sequence-points.html

Ну ладно. Давай посмотрим твой пример.
f(b(), c);

Как может быть выполнен код?
1. result_b = b(), c, f(result_b, c)
2. c, result_b = b(), f(result_b, c)

Далее. Почему ты считаешь что код вида f(std::shared_ptr<T>(new T), b())
будет выполнен в виде:
result_b = b(), ptr = std::shared_ptr<T>(new T), f(ptr, result_b)
или
ptr = std::shared_ptr<T>(new T), result_b = b(), f(ptr, result_b)

А не:
tmp_t = new T, result_b = b(), ptr = std::shared_ptr<T>(tmp_t), f(ptr, result_b)
?
Re[8]: Почему в этом коде течет память
От: Cyberax Марс  
Дата: 09.06.17 07:11
Оценка:
Здравствуйте, _Artem_, Вы писали:

_A_>А не:

_A_>tmp_t = new T, result_b = b(), ptr = std::shared_ptr<T>(tmp_t), f(ptr, result_b)
_A_>?
Потому, что вызов функции (std::shared_ptr::constructor()) создаёт точку следования (как и во всех остальных примерах).

В данном случае будет такая ситуация:
1. (tmp_t = new T(); std::shared_ptr::constructor(tmp_t);)
2. (b());


Но при этом, относительно друг друга выражения 1. и 2. не имеют заданной последовательности.

Чтобы получить неопределённое поведение нужно что-то типа:
static int global = 0;

void bar() {
   std::cout << global;
}

void increment() {
   global++;
}

foo(global(), bar());

Хотя и тут я не уверен, что оно будет.
Sapienti sat!
Re[9]: Почему в этом коде течет память
От: _Artem_ Россия  
Дата: 09.06.17 08:47
Оценка:
Здравствуйте, Cyberax, Вы писали:

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


C>В данном случае будет такая ситуация:

C>
C>1. (tmp_t = new T(); std::shared_ptr::constructor(tmp_t);)
C>2. (b());
C>


Все-же, предлагаю посмотреть статью Алены, вот цитата оттуда:

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

    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') вычисляется и все побочные эффекты от такого вычисления завершаются. Потом, если все значение всего выражения известно, правое выражение не вычисляется, иначе вычисляется.
    Правило слево-направо не работает для переопределенных операторов. В этом случае переопределенный оператор ведет себя как обычная функция. Еще такой момент — ?: не может быть переопределен по стандарту.
    (Упомянутая запятая это оператор запятая. Она не имеет никакого отношения к запятой, разделяющей аргументы функции.)


Получается, что operator new T может вызван отдельно от вызова std::shared_ptr<T>(). Их может разделить вызов функции b(), которая может сгенерировать исключение.
Re[7]: Почему в этом коде течет память
От: · Великобритания  
Дата: 09.06.17 09:57
Оценка: +1
Здравствуйте, Cyberax, Вы писали:

C>·>О чём же тогда Item 21?

C>А это просто ошибка. Я даже не представляю как можно получить неправильное поведение в данном примере, так как вызовы функций тоже создают точки следования.
Что-то сомневаюсь, что Саттер так налажал и никто до сих пор его не поправил. Во-первых, тут ведь не "оператор запятая", а вызов ф-ции с несколькими параметрами. Параметры могут вычисляться в произвольном порядке.
Другое дело, вопрос в том виде, который был приведён выше от JetBrains — неправильный, там другая ситуация, не такая как в Item 21 и утечек нет, вроде как.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: Почему в этом коде течет память
От: uzhas Ниоткуда  
Дата: 09.06.17 10:03
Оценка: +1
Здравствуйте, Cyberax, Вы писали:

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


C>>>Не может. Запятая — это точка следования.

_A_>>А вот и может.
C>Нет. Пункт Стандарта 1.9/17, первая запятая вносит точку следования.

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

в 1.9/17 (если я правильно понял из какой версии стандарта надо взять) мы видим:

When calling a function (whether or not the function is inline), there is a sequence point after the evaluation
of all function arguments (if any) which takes place before execution of any expressions or statements in
the function body.


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