All streams
Search
Write a publication
Pull to refresh
-29
@LordDarklightread⁠-⁠only

Пользователь

Send message

Проблема в том, что механизм питания черных дыр устроен иначе - чем они больше едят - тем их аппетит становится всё шире и шире! Это как растущий организм - его кормят - он растёт - и ему требуется всё больше и больше пищи - чтобы расти ещё больше!

Ну, Git, для небольшой команды, может показаться, что и не особо нужен. Но тут, скорее вопрос в современных тенденциях, и освоить Git стоит пораньше, потом просто будет сложнее на него перейти.
Ну и даже для небольших команд некоторые фишки Git - например, многоветочность, - очень даже будут полезными. А мерджи очень хороши в случае когда в проекте есть, условно говоря, сторонние библиотеки в исходниках - которые периодически нужно обновлять, и в которые тоже вносятся изменения хоть командой, хоть даже одним программистом по месту их потребления!

А вот, за что минус выше поставили - я так и не понял!

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

Ловить исключение Вы можете где угодно. Хоть сразу, хоть выше по стеку. В С++ объекты в куче сами собой как раз не уничтожаются. Объекты, размещённые в стеке, нам не интересны.

И всё-таки тут речь идёт об генерации исключения при выделении памяти (или создании объекта в куче) - память кончился - выделение не сработало - значит уже так и так деваться особо не куда - нужно выкручиваться - тут восстановление работы программы априори не может гарантироваться 100% - так что условные "зависания" случаются и на оборудовании - правильно написанный код позволяет откатиться к "началу" - высвободить память и перезапустить основной алгоритм. Исключения позволяют выкручиваться там - где это будет удобно (даже в самом начале всего приложения; и уже тем более грамотно выходить из библиотек), попутно стек будет ещё и раскручиваться и там в файнал блоках могут быть очищены какие-то объекты.

 прошивка ЭБУ выполнила недопустимую операцию и контроллер будет выключен? :)

В этом вся фишка исключений и такого ПО - можно откатиться к его началу и перезапустить. Это куда лучше - чем неверная работа с памятью приведёт к неверной работе всего приложения!

Но я ещё раз подчеркну и выделю: исключения не панацея - можно разрабатывать и без них - и я написал свой комментарий в самом начале не для продвижения исключений, а для описания действий, что можно делать при нехватке памяти - как переходить к нужным алгоритмам - это дело десятое - зависит от архитектуры - но без исключений программный код станет куда сложнее и запутаннее - скорее всего будет куча безусловных/условных переходов, или сложная ручная раскрутка стека и анализ возвращаемых значений - где допустить ошибку будет куда проще! Причём ошибки легко могут быть такими, что не приведут к краху приложения в их месте возникновения - но дальнейшая работа приложения окажется нестабильной и некорректной!

А разве при написании программ бывает иначе?

Просто увеличится уровень ответственности и того, что программист должен будет контролировать самостоятельно - от чего будут расти и риски ошибок программиста из-за человеческого фактора!

В некоторых ситуациях объект исключения тоже выделяет память в момент вызова, поэтому бросить std::bad_alloc не получится - приложение перейдёт в бесконечный цикл аллокации исключения и упадёт с stack overflow.

Как написал выше - вероятность что свободная память будет около нуля - сама около нуля - и хоть немного правильная архитектура программного кода не будет допускать такой ситуации. Но даже если и так - то кризисный код мог быт и такое обработать - просто перед выдачей исключению попытавшись освободить хоть что-то, до чего сможет дотянуться легко и просто - без выделений памяти на куче. Но лучше, конечно, вообще такого не допускать! А если у Вас код без исключений - то у Вас так же будут проблемы с невозможности выделить память на куче для какой-то ручной обработке!

В некоторых ситуациях объект исключения тоже выделяет память в момент вызова, поэтому бросить std::bad_alloc не получится - приложение перейдёт в бесконечный цикл аллокации исключения и упадёт с stack overflow.

Почитайте об embedded разработке, в частности - bare-metal, полезно будет, там нет операционной системы, там нет глобального пула памяти, там невозможны исключения в принципе.

Нет исключений - ну не пользуйтесь - я же не против!

Это не крутость или простота - это одна из областей где С и С++ находят большое применение сейчас.

Да да - Си и C++ безусловно лидеры при низкоуровневой работе с оборудованием , в т.ч. вне классических ОС - и там полно своих нюансов. Ничего не имею против - но всё что я написал в самом начале обсуждения касается и этих систем - если там нет исключений - то и пофиг - всё можно делать и без них

Специально нашёл какой-то древний сайт на народе (оказывается он ещё жив) про Turbo Pascal, а это древность времён MS-DOS.

http://www.borlpasc.narod.ru/docym/br/8/8_15.htm

Так и не смог понять, что Вы этим хотели показать - да эти функции в паскале давно - просто ранее изначально в них не было глобального флага переключателя - мне кажется это уже под конец борланд придумал. А до этого они исключение генерировали - но я могу ошибаться. Но это всё не особо важно!

В С и С++ существует множество исключений, которые программа вообще не сможет поймать средствами языка

Ну.... я не спец по C++ и тут нет дела мне до прочих исключений. Я конкретно говорил только о ручной генерации исключения "Out of memory" и его ручной обработки! Как одной из возможностей выхода их кризисной ситуации когда malloc вернул null (ну или исключение гарнирует оператор new в C++). И файнал блоки тут прекрасно всё отработают при раскрутке стека!

Просто - тут либо стек сам раскручивается в модели исключений, либо Вы его раскручиваете самостоятельно! Ну или делает безусловные переходы из подпрограмм и сами решаете вопросы исправления стека и очистки памяти!

Честно - мне всё это пофиг в данном обсуждении - каждому приложению свой подход - я лишь говорил что делать - при выходе на точку программного кода, где будет решаться данная ситуация с нехваткой памяти (или что делать прям сразу - не отходя от "кассы", т.е. от null вернувшегося из malloc).

Go и Kotlin точно не являются альтернативами Си, т.к. у них есть 

Ну так Вам более защищённая альтернатива нужна - с управлением памятью - или полная свобода маньяка-мазохиста, где кодинг - это хождение по краю пропасти!

У Rust тоже есть управление памятью - но да - переложенное на плечи программиста и компилятора - маньякам-мазохистам понравится!

В Kotlin Native, кстати вроде не GC а подсчёт ссылок. Да и новая модель работы с памятью там есть - правда пока не разбирался как устроена - но вроде бы тоже что-то на основе владения

Лично мне концепция мерджей больше нравится, чем блокировка (я вообще не сторонних процесса блокировок где-бы то ни было - я за параллельность и свободу работы). Но так как я с не работал с Git - то не понимаю некоторых тонкостей

И чем это принципиально отличается от провери врезультата malloc() по месту вызова?

Смотрите выше уже отвечал

Более того, исключение полностью ломает стек вызова, а значит логику восстановления (roll out) очень сложно реализовать - поэтому во многих сферах исключения запрещены к использованию.

Я, честно, не в курсе этой проблемы - мне тут нечего сказать. Но от исключений всегда можно легко перейти к линейной обработке - блоком обработки этих исключений - главное понимать что Вы готовы обработать в данном кадре стека! А файнал блоки доделают остальную работу - если будет исключение, которые Вы тут обрабатывать не готовы.

Ну и можно же попросить new не бросать исключения, тогда результат нужно будет проверять точно так же как и в случае с malloc() и это, между прочим, стандартная практика в embedded и подобных системах.

Я не спец по C++ - но согласен так можно - но это Вы уже специально и явно это делаете в своём коде - и несёте за это ответственность! Ну и на сторонний код - это может как повлиять, так и не повлиять - это как в C++ уж напишете - там свободы много - как и вариантов последствий с высокой сложностью поиска непонятных плавающих ошибок и неявного, трудно выявляемого поведения!

Поймите, я ни в коей мере не пытаюсь никого убедить, что исключения - это панацея во всех ситуациях! Но с ними - всё-таки проще, а главное надёжнее выкрутиться в подобной ситуации (и от них всегда можно перейти к модели без распространения исключений, в C++ это проще простого сделать, как вот выше Вы и приводите один из примеров). Если программист крут и разработка очень ответственно ведётся - то можно и без исключений! Я не против! Суть то не в этом. Я то просто ответил на то, что делать когда память закончилась - именно на такой пост я дал свой изначальный ответ! И я ставил исключения как самое важное что тут может быть!

Но, всё же - для относительно простых проектов - исключения (и какая-то их обработка выше по стеку) - самое лучшее решение!

getmem - Allocate new memory on the heap (аналог malloc)

new - Dynamically allocate memory for variable

Ну это, кажется уже современные хуки. Внешний переключатель поведения функции. Ранее только исключение было, кажется

И в описании функции getmem ошибка - что исключений эта функция не создаёт - как раз-таки может создавать - судя из описания (в описании процедуры new всё ок)!

Ну вот же, сперва отрицание, а потом правильный ответ :)

Я Вас не понял - что Вы имели в виду?

Про ребейз я так и не понял - что в деталях делает эта операция. В примере произведена подмена терминологии: текущая и целевая ветка на: основная и новая; а термин ребейз заменён на слияние Что т там с коммитами D и Е не понятно. Почему пропадает коммит E? Вообще в картинках явно есть серьёзный огрех: все узлы отображаются одинаково и трудно отличить коммит от чего-то ещё!

Остался непонятным вопрос и того, как несколько людей работают над одной веткой и решают конфликты коммита изменения одного и того же объекта?

Так же остался непонятым процесс мерджа - понятно, что в этом процессе посути данные из одной ветке (или нескольких веток) переносятся в другую ветку? Но что делать дальше с исходными ветками? Чаще всего они нуждаются и в обратном мердже? Это так же надо делать отдельно? И ещё раз выполнять весь процесс слияния , в т.ч. обрабатывать все конфликты (и не говорите что их никогда не будет в этом случае)?

Наверное ещё много чего осталось мной не понятно о чём я не понимаю как спросить или стесняюсь своей неграмотности в этом вопросе. Для новичков статья плохо подходит, на мой взгляд (а я хоть с Git и не работал, но с системами версионирование работаю уж как лет 20)

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

Вот это правильное замечание - но опять же - это критично если память уже совсем кончилась - а это, как я написал, крайне мало вероятно. Но да - к файнал блокам и деструкторам нужно относится внимательно (и об этом в часто пишут в доках к ЯП) - чтобы много памяти не пытались выделять. В идеале - я уже написал выше - надо как можно раньше выставлять маркер режима работы с нехваткой памяти (как минимум две стадии: просто когда памяти крайне мало, и когда её уже критически мало) - и учитывать его в файнал блоках. Стараясь передавать обработку в особую единю секцию кода - специально созданную для такой ситуации, работающую в основном либо с уже созданными ранее объектами, либо размещая их на стеке; либо управляя отдельным выделенным резервным буфером памяти - задача которого аккуратно освобождать переданные ему ресурсы; а в первую очередь кэш разного уровня глобальности и приоритетности. И всё под контролем текущего объёма свободной памяти. То есть, в таких проектах нужно сразу вводить такой маркер и проверять его в особо ответственных местах местах, а не разбираться в исключениях

Короче - как уже выше сказал - что делать в ситуации когда Out of memory - это всё тянет на целую весьма серьёзную статью! У тут у нас просто бла-бла-бла... кстати, что там Страуструп - ничего не писал на эту тему (я его не читал, к сожалению, C++ не мой любимый ЯП, скорее наоборот; хотя истоки проблемы как раз тянутся от языка Си - но у, прочитанного мной Подбельского, вроде ничего на эту тему не было)?

но насколько корректной будет обработка в в десятке-другом уровней выше по стеку

Она либо будет - что уже хорошо. Либо приложение завершится - что не самое плохое - если ничего не будет

 (например, какой нибудь деструктор или секция finally начнёт что-то писать в логи, попытавшись при этом выделить ещё несколько байтиков...)

Я выше много написал о том, что такая ситуация почти невероятна - если об этом вообще задумываться - т.е. целенаправленно обрабатывать исключение "Out of memory" не будучи при этом полным дилетантом! А полным дилетантам обрабатывать такие ошибки самостоятельно не стоит в принципе (как и допускать их к таким проектам - где это важно)

Да и в чём тут преимущество - если обрабатывать не исключение, а возврат malloc? Обработку которого пропустить куда проще (ну если нет автоматизации этого процесса)

А ещё - исключения могут возникать в стороннем коде и в библиотеках (что правильно для них) - так что их так или иначе надо обрабатывать!

Проверять код возврата malloc() или ловить исключение - это уже не так существенно.

Вы не правы. Тут принципиальная разница:

  1. Если Вы не будете проверять код возврата malloc - ваше приложение может вести себя крайне не опасно в критической ситуации. Причём проверять нужно сразу - в каждом месте использования оператора (либо писать макрос - и использовать везде) - легко где-то пропустить! Да и боллерплейт кода много писать нужно.

  2. Если не проверять исключение - то ваше приложение в худшем случае попросту упадёт. И проверять исключение тут можно в любой точке раскручиваемого стека - даже в нескольких - легко можно контролировать все ключевые места проверки и обработки. Да и исключение позволяет легко выйти из более глубокого кода - в какое-то более понятное место, где и произвести обработку. Попутно ещё и ресурсы высвобождая (в файнал секциях - которые надо предусмотреть, при работе в модели исключений; ну у Go - там своя кухня обработки ошибок - скорее близкая к п.1. - она мне не нравится; но при невозможности выделения памяти Go будет создавать "исключение" - панику - её можно обработать "особым образом")

Тот же Rust с памятью обращается более бережно. Как и Go (там свой менеджер памяти) или Kotlin Native (тут вроде бы тоже свой менеджер памяти)

Ну и многие современные ЯП (и ряд старых) тоже аккуратнее относятся к выделению памяти - не возвращая ноль - а генерируя исключение - а его уже проигнорировать будет куда сложнее!

То есть - это по большей части проблема Си и чуть меньше C++ (где правильно выделять память через оператор new - а он, вроде бы генерирует исключение, но я, честно, точно не помню этого)

Вроде как все современные ЯП исключение генерируют - ну а дальше как обработаешь!

вроде даже C++ командой new - будет ошибку генерировать. То есть проблема только в низкоуровневом malloc (и подобным) - прямого доступа к которому в современных, даже нативных, ЯП нет.

Если я не ошибаюсь (поправьте, коли не так)!

Старый Паскаль или Бейсик, или Фортран, вот изначально не имели такого доступа - и всегда генерировали исключение (но не всегда его можно было обработать - это уже прерогатива относительно современных реализаций).

Все управляемые и интерпретируемые ЯП жёстко контролируют работу с памятью и генерируют исключение!

Ну уже как обрабатывать исключение - это дело второе - сразу по месту возникновения (в т.ч. в своей фабрике создания объектов и выделения памяти) или где-то выше по стеку. Главное - что код, где возникла проблема, никак сам по себе продолжить своё выполнение не сможет! Ну только если ошибка явно будет обработана и далее явно будет разрешено выполнение - поэтому нельзя писать обработчики ошибок, обобщённые сразу для всех ошибок (если они не будут передавать ошибку далее по стеку)

Очень аккуратно и сложно (если приложение большое, пишется разными командами) - надо гарантировать, что мы дойдём от проблемной точки до кода, который может что то освободить, ничего не аллоцируя по пути.

Ну почему же так строго.

Во-первых, "ничего не аллоцируя" не требуется - как написал выше: вероятность того, что в такой редкой ситуации ещё и свободная память будет около нуля - асимптотически стремится к нулю. Особенно, если код заранее будет периодически контролировать объёмы свободной памяти.

Во-вторых, алгоритм реанимации так должен быть составлен, что в первых же своих командах сможет быстро освободить хоть какую-то "лишне" занятую память - это не сложно архитектурно предусмотреть - все ссылки на нужные объекты должны быть заранее подготовлены. Да и про выделение резервного блока памяти я написал.

В-третьих, алгоритм реанимации так должен быть составлен, что все (ну или почти все) обхекты, что требуется выделить в куче ужезаранее должны быть там размещены - чтобы в критической ситуации не пришлось их там выделять. Всё остальное можно стараться выделять на стэке, а не в куче (как написал выше - нехватка стэковой памяти это уже другой сценарий). Или выделять в резервном блоке (память под который уже зарезервирована)

В-четверых, сложности в выделении памяти могут быть только при обращении к стороннему коду, который может условно бесконтрольно создавать в куче новые объекты - но тут уже вопрос тестирования - нужно заранее знать кто и сколько будет выделять в такой ситуации - и гарантировать наличие такой памяти (прибегая к другим средствам из моих пунктов), ну и тщательно выбирая такой сторонний код, что будет использован в такой ситуации.

В-пятых, как я уже сказал, обработка данной ситуации это исключительный случай - тут либо надо сразу переходить в блок кода, занимающийся его обработкой. Либо генерировать специфическое исключение - разматывать стек (где, само собой корректно должны быть написаны обработчики исключений, освобождающие лишние ресурсы), и, доядя дишь, до нужной точки - проводить уже обработку исключения по критическому сценарию - а там уже далее принимать решение - мягко завершаться, перезапускаться, сохранять или нет как-то контекст выполнения/пользовательские данные (а диск), и можно-ли как-то возобновить текущую работу без жёсткого перезапуска/завершения.

В-шестых, от разношёрстности команды тут не сильно всё зависит (кроме стороннего кода, условно без исходников, ну или куда лезть не хочется). Весь механизм создаётся одной, небольшой, согласованной командой (частью), в проекте со всеми лишь согласуются соглашения по критическим точкам обработки критических исключений (где оставляется вызов, условно, единого обработчика), способы реаллоцирования памяти (отдельный макрос), и некоторые нюансы "регистрации" и выделения не особо нужных данных и работа с ними, с учётом того, что они "вдруг" могут быть освобождены.

В-седьмых, всё это в таком виде имеет смысл только для очень важных, сложных и высоконадёжных проектов - как написал выше - простым программам это не требуется. А в сложных проектах различные нюансы работы с памятью должны командой прорабатываться достаточно тщательно, тщательно документироваться и тщательно проверяться на ревью.

В-восьмых, как я написал выше - всё-таки я считаю, что этим, в большинстве случаев, должен заниматься не программист, а платформа и компилятор!

Если у нас связный список (или дерево, или граф общего вида) с несколькими байтами полезной информации в узле, но большим количеством узлов - очень легко съесть всю память маленькими аллокациями.

Так не делают, во всех коллекциях стараются аллоцировать большими порциями. И потом, потихоньку использовать (причём чем больше занято - тем больше следующая порция). Иная архитектура - скорее всего грубая ошибка (ну или студенческий код). Как минимум так делают из-за производительности - и частая аллокация, и частые разрозненные переходы от блока к блоку памяти - могу заметно снижать производительность!

3, 4, 5, 6, 7, 8 - все эти пункты можно применять, когда  malloc вернул 0, но аккуратно! Но, лучше не пренебрегать и пунктами 1, 2 - тогда остальные пункты можно применять заранее! И это куда безопаснее, чем сразу столкнуться с тем, что  malloc вернул 0

если нельзя выделить даже несколько байт, 

  1. Для освобождения памяти выделять её не надо. Тут имеет смысл в том, чтобы заранее выделить память под объекты, с которые потребуется работать в такой ситуации.

  2. Имеет прозапас выделенный резервный блок памяти, например в 1Мб, вы сможете обезопасить себя в ситуации необходимости получить небольшую память при её критической нехватке: тут даже освобождать ничего не надо (или иметь свой продвинутый менеджер памяти) - просто возьмите нужную память с конца такого блока и уменьшите значение переменной его размера. Но это, конечно, в простейшем виде, лишь решение в кризисной ситуации - чтобы не падать, и хоть как-то продолжить работу и выделять память (пока кризис не будет преодолён или приложение не завершит аккуратно свою работу)

  3. Ситуация когда malloc вернёт 0 при выделении нескольких байт - крайне редкая (асимптотически стремящаяся к нулю) - и скорее это уже ошибка архитектуры приложения. Обычно такой командой всё-таки выделяют куда большего объёмы блоки памяти, ну или хотя бы вероятность того, что память кончится на выделении блока длиной в хотя бы в несколько килобайт куда выше, чем длиной в несколько байт. Поэтому и есть пункты 1, 2 - которые имеет смысл выполнять при выделении больших блоков памяти, и заранее готовиться к её критической нехватке

Ну тогда автор статьи (той что не на хабре) не прав - меня то за что минусовать

Вот оно в чём дело - вот теперь понятно

Конечно мелкому софту сильные заморочки не нужны - я выше в своём комментарии об этом написал. Но тут дело в том, что даже простое приложение без какой-либо проверки не упав сразу может нанести вред другим компонентам и приложениям ОС изменив их память! Поэтому даже у простого приложения хотя бы примитивная обработка должна быть - чтобы хотя бы просто сразу упасть - всё-таки нужна. И данная статья говорит, что проверка через "assert" даже упасть может не помочь!

Вроде как эта проблема только Си и C++ (и каких-то вариаций)

Вот управляемые приложения будут генерировать исключение "Out of memory" при всех попытках выделения памяти при её недостатке (ну у них просто нет такой низкоуровневой операции, интересно что там по факту в инструкциях машинного кода генерируется), как, скажем, и Delphi - и наверное много других более современных нативных ЯП.

Что, в очередной раз, говорит о том - что Си и С++ - это ЯП "далеко не для всех и вся" - а только для особо "повёрнутых гиков" - которые знают что делать с "гранатой без чеки, находясь внутри синхрофазотрона" при решении задачи "как безопасно устроить кольцевой салют, искрящийся со скоростью точно и строго: одна искра в 37 наносекунд - вопрос жизни и смерти"!

Интересно - а как себя поведёт тот же C++ на операторе new? - ведь в статье речь только о проблемах низкоуровневого выделении блока памяти!

Странно - у меня на 64битной ОС 32битные приложения падают, при попытке выделить более 2Гб на процесс - но это не мои приложения - сам уже очень давно этим экспериментально не интересовался - но заинтриговали - я попробую провести эксперимент

Если что я говорил, только про windows - под*nix не проверял (но там нет эмуляции 32битной среды в 64битной ОС, но как там выделяется память в 32битной среде не знаю)

 к проверке результата работы malloc отношения не имеет.

Да ладно! Почти все пункты связаны с обработкой ситуации, когда malloc вернул 0

Но да да - это всё скорее к статье, которая не была опубликована на Habr - но тут как раз началось обсуждение общей проблемы - а не того, что не стоит проверять результат как через "assert". Но мой ответ всё-равно по теме - он обосновывает почему так не стоит делать - и что можно было бы сделать вместо проверки через "assert" (применение которой лишает таких возможностей). В целом - это скорее задел на возможную статью какому-либо автору (но это не мой профиль, так что от меня пока не ждите):

  1. было: Почему на провеять результат malloc

  2. было: Почему не годится проверка через "assert" и как правильно проверять

  3. возможно: Что делать когда проверка показала, что malloc вернул нулевой указатель

ИМХО, правильно было вообще это всё публиковать одной содержательной статьи! Но у автора совсем другой кругозор - поэтому его статьи о несколько более узкие!

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity