last day (17 days later) » 

2:29 PM
6
A: Почему нельзя просто освободить память занимаемую элементами?

selyaЕсли кратко описывать принцип работы delete: Вызываем деструктор объекта (если есть) Говорим, что память "бесхозная" и доступна для выделения. После этого работа с "удаленным" объектом влечет за собой неопределенное поведение. Так вот. Как я понял, ваш вопрос в том, зачем нужен деструктор, т...

 
Хм, а если мы, не вызывая деструктор, просто «теряем» объект (допустим, мы к старому объекту больше не обращаемся), и используем его память под другой объект — это точно UB? Можно ссылку на стандарт или надёжный источник?
 
@VladD что-то вроде "выделим память под объект в 200 байт", "перестанем его использовать, запишем в начало этого куска памяти объект размером 150 байт, будем работать с ним"?
 
Ага, именно это. Очевидная утечка, но есть ли там UB?
 
@VladD да вот самому интересно стало. Мне кажется, что это не ud. По крайней мере до тех пор, пока мы не пытаемся удалить объект n2. Тогда, думаю, в зависимости от реализации, может освобождаться не весь кусок памяти... Думаю, примерно так. Ведь есть в стандарте что-то про то, что удалять объект-родитель - вполне валидная операция. Думаю, тут что-то похожее. Может вопрос создать...
 
@VladD, strict aliasing?
 
2:29 PM
@wololo мы же "теряем" первоначальный объект. Не думаю, что тут какие-то проблемы со strict aliasing'ом...
 
Можно подробнее, что означает "теряем"?
 
@wololo больше не обращаемся к старому объекту, см. комментарий 1. (Или 0, если так больше по-программистски)
 
Интерпретировать некоторый объект как объект другого типа — это и есть нарушение strict aliasing. Разве нет?
 
@wololo не совсем...
 
@wololo: Strict aliasing — хм, может быть. Нужно глянуть, как точно формулируется.
 
2:29 PM
@VladD, «и используем его память под другой объект — это точно UB?» если используете память как указатель на другой объект, отличный от char или std::byte, тогда UB за нарушение strict aliasing.
 
@ixSci можете мне, пожалуйста, объяснить, каким боком тут стрикт алиасинг, если мы больше не обращаемся к этой памяти как к объекту первоначального типа. Почему нельзя рассматривать это как B *b = static_cast<B>(malloc(sizeof(A)))? Ведь если мы больше не интерпретируем эту память как объект A, то это же просто кусок памяти...
 
@selya, в Вашем примере нет нарушения, т.к. у Вас вообще нет никаких типов. У Вас сырой кусок памяти, в котором ничего не создано. Создайте в этом куске B, потом преобразуйте в указатель на A и получите strict aliasing
 
@ixSci хорошо, я глянул стандарт. Чтение памяти, выделенной под объект A, через указатель на тип B - по стандарту уже UB (если это не несколько исключений, в которые входит char). Тогда скажите ещё, пожалуйста. Скажем, если выделить память под массив char, а потом маппить её указателями типа B - то это нормально, ибо это разрешает стандарт. А вот если я выделяю память под объекты своего типа A, то уже не могу маппить эту память указателями на B, так? Я не вижу ничего, что вело бы к UB, но мы должны иметь в виду, что на такой случай стандарт не даёт нам никаких гарантий, так?
 
@selya, по стандарту мы можем обращаться к объекту только через совместимый указатель (cv, отношения наследования и т.п.), либо же через указатель на char/byte, всё остальное — UB. Т.е ответ на Ваш последний вопрос — да, это UB.
@selya, только учтите, что мой последний комментарий относится к выделению памяти через new. Если разговор про обезличенное выделение (malloc), тогда «выделяю память под объекты своего типа A» не имеет смысла, т.к. выделяете Вы блок памяти, но пока там не создан объект, причислять эту память ни к A, ни к B нельзя, поэтому и разговоров о strict aliasing быть не может.
 
@ixSci: Хорошо, а как насчёт сценария «выделить память malloc'ом, разместить в ней при помощи placement new объект типа A, а затем в той же памяти объект типа B, и работать с ним по указателю на B»?
 
2:29 PM
@VladD, Если мы удалим A, потом создадим на этом месте B, то не вижу причин для UB, т.к. никакого aliasing уже не получается.
 
@ixSci: А если не удалим? А просто «забудем» об объекте? Вопрос как раз об этом.
 
@VladD, если не удалим, то можем получить UB. Можете посмотреть в стандарте и ещё одну интересную функцию: std::launder. Скользкая тема, на самом деле.
 
@VladD вроде бы обычная утечка памяти получится - разве нет?
 
@Qwertiy А strict aliasing точно не нарушается?
 
@VladD каким образом?
я бы сказал, что нет
 
2:48 PM
@Qwertiy Ну, вопрос в точной формулировке strict aliasing rule
Вот ixSci ссылается на стандарт выше.
Хм!
В стандарте как раз противоречащий пример
#include <cstdlib>

struct B {
  virtual void f();
  void mutate();
  virtual ~B();
};

struct D1 : B { void f(); };
struct D2 : B { void f(); };

void B::mutate() {
  new (this) D2;    // reuses storage — ends the lifetime of *this
  f();              // undefined behavior
  ... = this;       // OK, this points to valid memory
}

void g() {
  void* p = std::malloc(sizeof(D1) + sizeof(D2));
  B* pb = new (p) D1;
  pb->mutate();
  *pb;              // OK: pb points to valid memory
  void* q = pb;     // OK: pb points to valid memory
Пример после этого раздела: eel.is/c++draft/basic.life#6.5
Так что вроде бы UB нет? // @Qwertiy @ixSci
void B::mutate() затирает сея в первой же строчке
 
 
1 hour later…
3:56 PM
@VladD что-то я пример не понимаю ((
 
@Qwertiy Ну, там выделяется память p, потом на ней создаётся объект типа D1, а он в своём методе mutate выполняет на своей памяти конструктор типа D2.
То есть как бы *p меняет свой тип на лету.
 
@VladD так, и почему на этом нельзя вызывать виртуальную функцию?
и почему во втором примере время жизни закончилось?
 
4:24 PM
@Qwertiy А f невиртуальная
Стоп, виртуальная
Ну, наверное, чтобы копмилятор смог оптимизировать виртуальный вызов
Он-то точно знает тип объекта
Точнее, думает, что знает :-P
Если запретить такую оптимизацию, придётся запрещать девиртуализацию где угодно. А это performance penalty
 
4:42 PM
@VladD, хм, похоже, что переиспользование (reused) хранилища (storage), занимаемого некоторым объектом o, посредством placement new заканчивает время жизни объекта (lifetime of an object) o. А значит о нарушении strict aliasing речи не идёт, ибо объекта больше нет.
 
@wololo можешь объяснить, что за время жизни объекта?
@VladD, во втором варианте между верной строкой и ошибкой только присваивание указателя
это оно закончило время жизни?
если да, то я не понимаю
если нет, то я не понимаю, что его закончило
@VladD
 
@Qwertiy, время жизни описано в 6.6.3/1
@Qwertiy, насколько я понял время жизни закончилось, когда в mutate с помощью new на месте объекта разместили другой объект.
 
5:07 PM
Вот только почему вызов f() — UB, даже при том, что pb указывает на валидную память действительно не понятно...
@Qwertiy, "что-то я пример не понимаю" Ничего не поделать. Это C++ :)
 
5:24 PM
@wololo Хм, а почему должно быть валидно?
@Qwertiy Подожди, ты о каком варианте? Там он вроде бы только один.
 
@VladD, там может не быть UB, но в общем случае никаких гарантий. В той ссылке, что я привел, и что привели Вы приведены условия.
 
@Qwertiy По идее, закончила его строчка new (this) D2; // reuses storage — ends the lifetime of *this
@ixSci Это вы о каком коде?
 
@VladD это я о том, что можно ли переиспользовать память без удаления
а в примере UB убирается использование laundry, если я правильно понимаю
this считается мёртвым указателем, хотя и указывает на нормальный объект
или нет. В общем, код из стандарта мало вяжется с тем, что описано в параграфе, к которому тот пример привязан
там просто нет такого случая: Before the lifetime of an object has started but after the storage which the object will occupy has been allocated33 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released
 
5:47 PM
@ixSci Может быть, это илйюстрация к reused?
 
6:09 PM
@VladD, После выполнения этой строки: new (this) D2; что происходит с this? Если он валиден, то на объект какого типа он указывает?
 
@wololo угу, вот это и мой вопрос.
Думаю, что this в этой точке невалиден
Обосновать не могу (ц)
 
6:30 PM
@VladD, @ixSci, я верно понимаю, что после строки new (this) D2; 1) объекта, на который первоначально указывал указатель this больше нет, его время жизни подошло к концу; 2) теперь в том месте, на которое указывает указатель this расположен объект типа D2?
 
6:44 PM
@wololo да, так я это понимаю
@VladD но там весь пункт посвящён транзитивному состоянию, которого мы не наблюдаем
т.е. у нас нет ситуации, в которой память создана, но объект ещё нет, либо же ситуации, где объект уничтожен, а память не очищена/переиспользована
 
@ixSci, действительно, судя по всему, вы правы. Но я что-то не пойму... В пункте 6.2 говорится "the pointer is used to access a non-static data member or call a non-static member function of the object". Я так понимаю, строка f(); // undefined behavior предназначена для иллюстрации пункта 6.2? Но ведь память уже была переиспользована? Разве мы всё ещё в транзитивном состоянии?
 
@wololo этого я тоже не понимаю, возможно этот пример ошибочен. Но со стандартом нужно быть осторожным. В целом, можно попробовать задать вопрос на enSO и получить ответ от кого-нибудь из комитета или приближённых. Но мне, честно говоря, не очень интересно. Потому как я не могу представить зачем такой код может понадобится, а если он и понадобиться, то всё равно нужно будет смотреть на то, как будет вести себя конкретный компилятор, т.к. это очень тонкая штука.
 
7:18 PM
Как я понял, this не может быть использован, как объект (например, this->f(), ибо это ведет к UB (то же самое, что и с pb->f();) , однако, он указывает на валидный кусок памяти, так что указатель вполне может быть куда-либо передан и тд. Тогда возникает другой вопрос: а где взять валидный указатель на эту память после того, как в ней создали другой объект? Отсюда выдрать: void *p = new (this) D2; ?
 
7:33 PM
А простое static_cast<D2*>(p) не правильно?
 
Хм, p указывало на изначально выделенную память, так что думаю, что это вполне нормально. А вот reinterpret_cast<D2>(pb) уже будет UB
Как-то очень сложно, мда. Как я понял, можно руководствоваться простым правилом. Есть указатель. Если он изначально указывал на простой кусок памяти, то можно кастовать его к объекту. Если он был создан для того, чтобы указывать на объект, то кастовать его к указателю на объект другого типа - уже UB.
 
@selya Ох, как всё сложно-то! Я вот думаю, написал ли я в жизни хоть один проект на C++ без UB?
 
7:49 PM
Хах. Хороший вопрос. Но я думаю, что в большинстве случаев можно игнорировать UB, который в данном случае описывает стандарт (по крайней мере до тех пор, пока ваш код не захотят собрать каким-то эксклюзивным компилятором)
И кстати. Я не совсем понимаю термин object lifetime. Это просто название промежутка времени после вызова нетривиального конструктора и до вызова нетривиального деструктора?
И если предполагать, что указатель на объект валиден лишь в течение жизни объекта, то, как я и говорил, можно использовать этот указатель как просто указатель на память, до тех пор, пока компилятор не научился отслеживать obj lifetime в рантайме)
 
 
1 hour later…
9:16 PM
@VladD да.. что-то я зря думал, что их там 2
 
@selya Ну, судя по всему да.
@selya Он имеет право заинлайнить всё и отследить в компайлтайме по идее.
@selya Но как «просто указатель на память», то есть, void*, он должен быть валиден, пока память не вернётся аллокатору
@Qwertiy Угу, один хитрый вариант.
 
9:33 PM
@VladD Вот даже не знаю... Как void* должен быть валиден, да и как char* тоже. Но вот как указатель на объект другого типа, как я понял, он не должен быть использован, если верить вот этому
Я запутался...
 
@selya что-то вряд ли
зачем нужна память, которую нельзя использовать?
 
@Qwertiy Да у меня тот же вопрос...
 
10:23 PM
@selya Ну да, по-моему, так и есть.
@Qwertiy Зато её можно переиспользовать.
Например, выделить там другой объект
Или сравнить этот указатель с другим
 
@VladD как?
 
@VladD блин, выделить... Я читаю про placement new. Почему я могу сконструировать там другой объект, но не могу тупо маппить ее под другой объект, если уверен, что значения в этой памяти вполне валидны для объекта n2
 
@VladD но ведь в примере он невалиден?
@selya потому что виртуальные функции?
 
@Qwertiy а эта информация хранится в куске памяти объекта, да?
 
@selya не уверен
может быть немного снаружи
например, 8 байт перед ним
 
10:31 PM
@Qwertiy как длина для массивов, ага...
 
@selya размер указателя
указатель на таблицу виртуальных функций
 
@Qwertiy ну я имел в виду, что для массивов тоже обычно перед памятью самого массива хранится его длина, чтобы потом знать, сколько памяти очищать
 
@selya не факт
 
В таком случае, если стандарт явно не описывает то, как хранится указатель на виртуальные функции, то логично, что это UB, и все из-за виртуальных функций...
 
кстати, указатель на таблицу должен бы всё-таки внутри быть...
@selya не описывает, насколько я знаю
в любом случае, ты же валидность с точки зрения полей имел в виду, а не классов?
 
10:34 PM
да, полей
ну т.е. поля для класса будут выглядеть нормально
я тут вопрос задал на англоязычном стаке, но меня отправили как дубликат placement new) (мой английский ужасен, по всей видимости)
Короче. Как я понял - если мы выделили кусок памяти любым образом, то мы можем его переиспользовать, писать в него что угодно, нужно лишь быть уверенным в том, что никто не вызывает деструкторы на объектах, для которых изначально была выделена эта память. Ну и чтобы ее кто-то нечаянно не удалил - тоже нужно смотреть. Самое главное - после использования очистить ее тем способом, каким она была выделена. Как-то так я это все понял.
А про UB в стандарте написано для тех случаев, в которых нарушается strict aliasing. Выделяйте, перезаписывайте - в общем, творите безумие, пока понимаете, что делаете!
 
11:07 PM
  new (this) D2;    // reuses storage — ends the lifetime of *this
  f();              // undefined behavior
@selya тогда почему тут UB?
у меня же есть валидный указатель на объект типа D2, поскольку использован new
 
@Qwertiy А f же вызывается в контексте D1
 
@selya нет, в контексте B
 
@Qwertiy placement new?
 
а B - их общий предок, а указатель на потомка можно приводить к указателю на предка
@VladD ну так в примере оно и есть, а говорится про UB
 
@selya Ну, хм. Это значения валидны, а лэйаут классов может быть разным.
@Qwertiy Не, в примере он валиден. А вот обращение к f внутри функции mutate невалидно
 
11:12 PM
@VladD так почему??
я не вижу причин, по которым оно невалидно
 
@Qwertiy потому что f() есть this->f()
а this после placement new невалиден
судя по всему, логика стандарта такая
 
но this после placement new указывает на D2
 
@Qwertiy нет, this после placement new вообще не валиден. у него рантайм-тип «указатель на D1»
так что, думаю, его использование не стрикт алиасится
 
@VladD с какой стати на D1? указатель на B - мы же в B находимся
 
@Qwertiy я имею в виду реальный, рантайм-тип
 
11:14 PM
@VladD э? что такое рантайм-тип?
 
давай рассмотрим пример попроще
 
@VladD давай)
я только рад буду, если проще)))
 
вот у меня есть указатель на объект
я убиваю объект
пользоваться указателем больше нельзя
теперь, я создаю на месте этого объекта другой объект того же типа
оживляет ли это указатель?
 
хороший вопрос
 
@VladD должен
 
11:17 PM
а тут у нас ещё и вообще другой тип
 
а разве нет?
 
у которого имеет право быть другой лэйаут
сомнительно как-то
я бы задал вопрос спецам, с тегом .
потому что это именно оно
 
@VladD откуда? он же наследник? хотя.. если бы там множественное наследование было...
@VladD ну давай)))
 
@Qwertiy но уже наверное не сегодня
т. к. надо спать :)
 
@VladD есть ещё 42 минуты чтобы успеть до получночи по UTC
или тебе наоборот завтра надо?)
 
11:19 PM
@Qwertiy у меня уже завтра
:)
вопрос не в баллах, просто хочу спать уже
а формулировать вопрос долго
 
@VladD просто для некоторых знаков выгодно задавать вопросы в разные дни)
 
надо бы спросить не только о букве стандарта, но также и о его мотивации
 
@VladD ага)
 
@Qwertiy Огооооо! Моё зодиак-фу ещё не настолько сильно.
Я побежал спать, а про вопрос надо не забыть завтра
 
@VladD там что-то типа "задал n вопросов в разные дни и поддерживал по ним положительный рейтинг" или что-то такое
 
11:21 PM
@Qwertiy А, вот оно как! Был такой беджик
 
@VladD ok, спокойной ночи :)
 
@Qwertiy Давай, до завтра! Разберёмся завтра
@selya Спокойной ночи!
 
@VladD ru.stackoverflow.com/help/badges/89/socratic - по золотому значку за 100 дней :)
@VladD ага, я тоже спать пойду - полчетвёртого почти))
хм.. а у тебя его нет о_О
 
Спокойной всем
 

  last day (17 days later) »