что выполняет код?: container создает некоторое кол-во объектов типа item. item`ы в свою очередь, удаляются по событиям производимым в ответ на сетевое взаимодействие, т.е. асинхронно. (тут в примере — по таймеру)
в чем вопрос?: вопрос в том, что как вы можете видеть в выводе(ниже), когда item по адресу 0xda6370 зовет 'container::delete_me(id)' для себя же, что происходит со стеком? стек ведь должен размотаться до уровня, из которого вызвали 'container::delete_me(id)', но объект же удалится раньше чем стек размотается %)
item 0 created. time=2 (0xda6110)
item 1 created. time=1 (0xda6370)
time-out for item 1 expired, request for delete it (0xda6370)
before erase item 1 (0xda6370)
dtor item 1 (0xda6370)
after erase item 1 (0xda6370)
time-out for item 0 expired, request for delete it (0xda6110)
before erase item 0 (0xda6110)
dtor item 0 (0xda6110)
after erase item 0 (0xda6110)
finished!
спасибо.
зы
я плохо знаю ассемблер .и наверное по этому и не понимаю, валиден ли код или нет...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: удаление вызывающего объекта, и непонятка с размоткой стека
Здравствуйте, niXman, Вы писали:
X>в чем вопрос?: вопрос в том, что как вы можете видеть в выводе(ниже), когда item по адресу 0xda6370 зовет 'container::delete_me(id)' для себя же, что происходит со стеком? стек ведь должен размотаться до уровня, из которого вызвали 'container::delete_me(id)', но объект же удалится раньше чем стек размотается %)
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>В твоём случае он не удаляет "себя же", это делает "ios.run();" :
почему же?
нет, я понимаю как работает io_service::run(), но цепь вызовов у нас такая: timeout_handler()->container::delete_me()->map::erase()->delete <item address>
EP>P.S. не надо лезть в итератор после erase.
я сначала убедился в том, что адрес выводится правильный, и только потом решил почистить код от дополнительной переменной.
и суть не в этом
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: удаление вызывающего объекта, и непонятка с размоткой стека
Здравствуйте, niXman, Вы писали:
EP>>В твоём случае он не удаляет "себя же", это делает "ios.run();" : X>почему же? X>нет, я понимаю как работает io_service::run(), но цепь вызовов у нас такая: timeout_handler()->container::delete_me()->map::erase()->delete <item address>
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Ок, давай рассмотрим вот такой вариант:
т.е. тут ты акцентируешь внимание на том, что io_service::run() замыкает хендлер на основной поток выполнения, так?
но все равно не понимаю, что общего у твоего примера, и моего %)
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[5]: удаление вызывающего объекта, и непонятка с размоткой стека
Здравствуйте, niXman, Вы писали:
EP>>Ок, давай рассмотрим вот такой вариант: X>т.е. тут ты акцентируешь внимание на том, что io_service::run() замыкает хендлер на основной поток выполнения, так? X>но все равно не понимаю, что общего у твоего примера, и моего %)
Вот call stack для твоего примера (MSVC2010SP1 Debug):
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Что конкретно смущает?
не понимаю, что ты хочешь сказать. вроде как, твой ответ не отвечает на мой вопрос. или я не "туда смотрю"...
возможно мы говорим о разном.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: удаление вызывающего объекта, и непонятка с размоткой стека
X>что выполняет код?: container создает некоторое кол-во объектов типа item. item`ы в свою очередь, удаляются по событиям производимым в ответ на сетевое взаимодействие, т.е. асинхронно. (тут в примере — по таймеру)
X>в чем вопрос?: вопрос в том, что как вы можете видеть в выводе(ниже), когда item по адресу 0xda6370 зовет 'container::delete_me(id)' для себя же, что происходит со стеком? стек ведь должен размотаться до уровня, из которого вызвали 'container::delete_me(id)', но объект же удалится раньше чем стек размотается %)
чтобы понимать, что происходит лучше конечно знать в какой машинный код транслируется твой пример. и да, это понимание требует знания машинных команд и практики работы с ними (реверсинг С++ного кода, а также желательно написание своего кода на "голом" asm, чтобы прочуствовать все на свое шкуре )
возвращаясь к твоему вопросу: что ты позразумеваешь тут под "размоткой стека" ? ) когда по твоему и самое главное почему это должно происходить? -- вообщето данный терм относится к теме исключений, но речь ведь сейчас не о них!
когда ты в С++ пишешь вызов функции, в машшинных командах это означает следующие шаги:
0) засунуть параметры функции в регистры/стэк (что и куда, в общем случае, зависит от calling convention)
1) выполнить инструкцию call, которая положит в стек (возможно в добавок в параметрам) адрес возврата, и передаст управление по адресу функции/метода
2) далее выполняется пролог вызванной функции (организация stack frame: push %ebp; mov %esp, %ebp)
3) выполняется собственно функция
4) выполняем эпилог функции: возвращаем ebp в состояние которое он имел до вызова (pop %ebp)
5) выполняем инструкцию ret, которая возвращает нас по адресу сохраненному в вершине стека (вытаскивая его оттуда)
6) после возвращения из подпрограммы, согласно Сишному CC, вызывающая сторона, убирает из стека, засунутые туда параметры (ибо, в общем случае, только она и знает сколько их было и какого размера)... благодаря этому кстати в С возможны функции с неопределенным числом параметров (ellipses), и не возможны в pascal CC, где подпрограмма сама убирает из стека переданные ей параметры перед возвратом (ну или во время его, для чего инструкция ret, позднее была сделана с параметром: на сколько подвинуть стэк перед возвратом).
т.е. с точки зрения машинных команд нет ни каких объектов с методами -- есть просто куча "подпрограмм" которые принимают какие-то параметры и работают с какими-то данными, и методы от свободных функций на этом уровне не отличаются ничем! (просто первый параметр у них (неявный с точки зрения С++) это this указывающий на экземпляр объекта).
таким образом, после того как ты опосредованно удалил сам себя (не будем пока говорить о прямоте данного дизайна), важно соблюдать "гигиену" в коде, и не пытаться обращаться к своим дата-мемберам и виртуальным функциям (это ессно бует UB)... хотя в некоторых случаях и это сканает, потому что удаленный объект не сразу может превратиться в мусор, нужно чтобы кто-нить (из вызываемых далее функций) аллоцировал что-нить, "порушив" освобожденную только что память) -- но в любом случае это UB, и поиск подобного бага то еще развлечение
теперь о главном: практически всегда извраты подобные этому -- это проблемы в понимании и сопровождении такого кода (даже в таком тупом случае как 'delete this;'! нужно четко себе представлять life time всех участвующих объектов, что в мясном коде (boost::asio + куча асинхнронных callbackов) однозначно вызовет проблемы если не с написанием , то при сопровождении (даже если это будешь ты сам, но после нескольких месяцев а может и лет... не говоря уже о других людях
старайся избегать подобных наворотов -- будь проще! сделай так, чтобы удалением занимался сам контейнер, а не "тупой" item, который ничего не знает (да и не должен) о том где он хранится, сколько ему отведено жить, и т.д. -- это все находится в ведении контейнера: он единоличный хозяин всех itemов! он может кому-то дать "попользоваться" своей "собственностью", отдав weak_ptr "наружу", или может "подарить" кого-то из itemов, явно (!) передав владение... но давать itemам возможность самоубиться, это глупо и неправильно!
Re[2]: удаление вызывающего объекта, и непонятка с размоткой стека
Z>возвращаясь к твоему вопросу: что ты позразумеваешь тут под "размоткой стека" ? ) когда по твоему и самое главное почему это должно происходить? -- вообщето данный терм относится к теме исключений, но речь ведь сейчас не о них!
Z>когда ты в С++ пишешь вызов функции, в машшинных командах это означает следующие шаги: Z>0) засунуть параметры функции в регистры/стэк (что и куда, в общем случае, зависит от calling convention) Z>1) выполнить инструкцию call, которая положит в стек (возможно в добавок в параметрам) адрес возврата, и передаст управление по адресу функции/метода Z>2) далее выполняется пролог вызванной функции (организация stack frame: push %ebp; mov %esp, %ebp) Z>3) выполняется собственно функция Z>4) выполняем эпилог функции: возвращаем ebp в состояние которое он имел до вызова (pop %ebp) Z>5) выполняем инструкцию ret, которая возвращает нас по адресу сохраненному в вершине стека (вытаскивая его оттуда) Z>6) после возвращения из подпрограммы, согласно Сишному CC, вызывающая сторона, убирает из стека, засунутые туда параметры (ибо, в общем случае, только она и знает сколько их было и какого размера)... благодаря этому кстати в С возможны функции с неопределенным числом параметров (ellipses), и не возможны в pascal CC, где подпрограмма сама убирает из стека переданные ей параметры перед возвратом (ну или во время его, для чего инструкция ret, позднее была сделана с параметром: на сколько подвинуть стэк перед возвратом).
Z>т.е. с точки зрения машинных команд нет ни каких объектов с методами -- есть просто куча "подпрограмм" которые принимают какие-то параметры и работают с какими-то данными, и методы от свободных функций на этом уровне не отличаются ничем! (просто первый параметр у них (неявный с точки зрения С++) это this указывающий на экземпляр объекта).
это все я понимаю, в теории. но сам никогда не писал на асме, и многих деталей не знал. но, в общем, так я и представлял все эти внутренности.
Z>таким образом, после того как ты опосредованно удалил сам себя (не будем пока говорить о прямоте данного дизайна), важно соблюдать "гигиену" в коде, и не пытаться обращаться к своим дата-мемберам и виртуальным функциям (это ессно бует UB)... хотя в некоторых случаях и это сканает, потому что удаленный объект не сразу может превратиться в мусор, нужно чтобы кто-нить (из вызываемых далее функций) аллоцировал что-нить, "порушив" освобожденную только что память) -- но в любом случае это UB, и поиск подобного бага то еще развлечение
Z>теперь о главном: практически всегда извраты подобные этому -- это проблемы в понимании и сопровождении такого кода (даже в таком тупом случае как 'delete this;'! нужно четко себе представлять life time всех участвующих объектов, что в мясном коде (boost::asio + куча асинхнронных callbackов) однозначно вызовет проблемы если не с написанием , то при сопровождении (даже если это будешь ты сам, но после нескольких месяцев а может и лет... не говоря уже о других людях
Z>старайся избегать подобных наворотов -- будь проще! сделай так, чтобы удалением занимался сам контейнер, а не "тупой" item, который ничего не знает (да и не должен) о том где он хранится, сколько ему отведено жить, и т.д. -- это все находится в ведении контейнера: он единоличный хозяин всех itemов! он может кому-то дать "попользоваться" своей "собственностью", отдав weak_ptr "наружу", или может "подарить" кого-то из itemов, явно (!) передав владение... но давать itemам возможность самоубиться, это глупо и неправильно!
нет, я не собирался такой код написать и пустить в продакшн, нет
просто несколько раз натыкаюсь на то, что существующий дизайн пытается меня заставить сделать именно это.
это не страшно, ибо придерживаюсь правила: "кто ресурс выделил, тот его и освобождать должен" или "ресурс должен освобождаться на том же уровне, где был захвачен". и в этом духе.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[2]: удаление вызывающего объекта, и непонятка с размоткой стека
Здравствуйте, zaufi, Вы писали:
Z>) чтобы понимать, что происходит лучше конечно знать в какой машинный код транслируется твой пример. и да, это понимание требует знания машинных команд и практики работы с ними (реверсинг С++ного кода, а также желательно написание своего кода на "голом" asm, чтобы прочуствовать все на свое шкуре )
Полностью согласен. Опыт реверсинга мало того что крайне полезен, так ещё и жутко интересен — bruteforce решения во многих случаях неприемлемы (требуют много времени), приходится постоянно "соображать".
Z>таким образом, после того как ты опосредованно удалил сам себя
Я выше привёл call stack, в этом примере нет "удаления самого себя". Есть просьба объекта, убить его через секунду, которая выполняется третьим лицом.
Re[3]: удаление вызывающего объекта, и непонятка с размоткой стека
Здравствуйте, niXman, Вы писали:
X>Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>Есть просьба объекта, убить его через секунду, которая выполняется третьим лицом. X>снова не понимаю %)
фактически это не твой item удаляет сам себя (вызывая delete_me у контейнера)... item всего лишь просит таймер, когда у того случится событие, вызвать лямбду, в которой заcaptureн cont, выполнить delete_me (в контексте неопределенного потока выполняющего io_service::run())... и да, сам таймер принадлежит item'у -- т.е. он будет убит в то время, пока его асинхронный handler все еще выполняется... т.е. если после этого вызова, таймер попробует чонить сделать с собой -- будет UB.
поэтому на всякий случай в деструкторе item'a я бы сделать ему cancel()... ну тоесть если бы у меня не было выбора (под дулом пистолета к примеру) и нельзя было бы передизайнить тут все к фигам...
Re[4]: удаление вызывающего объекта, и непонятка с размоткой стека
id=0 was born
asking Killer to kill id=0 later
id=1 was born
asking Killer to kill id=1 later
Killer: shooting id=0
I am dying! id=0
Killer: shooting id=1
I am dying! id=1
THE END
Re[5]: удаление вызывающего объекта, и непонятка с размоткой стека
Не надо дула пистолета. Нужно сделать у item'а валидные предсмертные состояния:
— "умираю" — перед входом в delete_me; объект ещё нельзя удалять, но уже нельзя использовать
— "умер" — по выходу из delete_me; теперь его и удалять можно (когда на него закончатся ссылки)
В предсмертных состояниях объект перестаёт содержательно реагировать на вызовы своих методов и, возможно, даже кидает исключения.
В состоянии "умер" объект отчислен из контейнера и более недоступен, кроме как по старым ссылкам.
Перекуём баги на фичи!
Re[6]: удаление вызывающего объекта, и непонятка с размоткой стека
дык никто и не спорит что можно "выкрутиться" и попытаться придать этому коду большую maintainability, введя состояние, проверку этого состояния во всех методах объекта item, выкидывание исключений (кстати куда? -- все выполняется в контексте одного из "тупых" thread'ов-worker'ов, который маскимум что сможет сделать это catch(...) и отспамить в лог... чтобы сделать на этом месте что-то вменяемое, нужно чтобы этот worker как-то передал пойманное исключение кому-то (кому?) кто сможет его обработать... нужен видимо какой-то диспетчер, который по типу исключения, возможно, сможет понять кому оно предназначалось, и каким-то волшебным образом "засигналить" тому объекту типа "эгей! тут тебе прилетело!" -- в целом все оч быстро превращается в нагромождение кучи кода "на пустом месте" (в зависимости от обстоятельств и желания реагировать на ошибки, меняется только размер "кучи")... это вместо того, чтобы следовать простому правилу "кто девушку ужинает (alloчит), тот ее и танцует (delete)" ))
Re[5]: удаление вызывающего объекта, и непонятка с размоткой стека
Здравствуйте, zaufi, Вы писали:
Z>фактически это не твой item удаляет сам себя (вызывая delete_me у контейнера)... item всего лишь просит таймер, когда у того случится событие, вызвать лямбду, в которой заcaptureн cont, выполнить delete_me (в контексте неопределенного потока выполняющего io_service::run())... и да, сам таймер принадлежит item'у -- т.е. он будет убит в то время, пока его асинхронный handler все еще выполняется... т.е. если после этого вызова, таймер попробует чонить сделать с собой -- будет UB.
а ведь да. понял.
всем спасибо, вопрос закрыт.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)