LINUX.ORG.RU

Вопрос по ручному управлению памятью

 ,


1

2

Я в этом не разбираюсь, но возник такой вопрос. Считается, что в языках с GC нет ручного управления, а, например в сишке есть.

Меня заинтересовал вот какой вопрос. Допустим, мы пишем команду «освободить память такую-то». Интересно, что реально тут происходит в этот момент? Исполнение останавливается до тех пор, пока память не будет освобождена, а затем возобновляется? В таком случае, у нас разница между языком с GC и без него только в том, что нет накладных расходов на сам GC, на алгоритмы подсчета ссылок и/или обход дерева. Непосредственно на расход памяти это не влияет, практически, так получается?

И, кстати, что представляет из себя процесс освобождения памяти, на более низком уровне?

ЗЫ пришлось поставить тег c++, поскольку тегов связанных с «просто си» не нашел вообще. Подскажите что поставить по си.



Последнее исправление: cetjs2 (всего исправлений: 2)
Ответ на: комментарий от eao197

В первый раз динамически создавая объект и размещая указатель на него в std::unique_ptr. Тем самым определяя время жизни объекта, а именно моментом разрушения этого самого unique_ptr.

auto foo()
{
    auto x = make_unique<A>(...);
    ...
    if (...) {
        auto y1 = make_unique<A>(...);
        ...
        if (...) {
            x = move(y);
        }
        ...
    }
    ...
    return x;
}

Для сравнение, ручное управление(надеюсь, понятно, что это лишь пример и придираться вы не станете):

A* foo()
{
    auto x = new A(...);
    try {
        if (...) {
            auto y = new A(...);
            bool del_y = true;
            try {
                ...
                if (...) {
                    delete x;
                    x = y;
                    del_y = false;
                }
                ...
            }
            catch (...) {
                if (del_y) {
                    delete y;
                }
                throw;
            }
            if (del_y) {
                delete y;
            }
        }
        ...
    }
    catch (...)
    {
        delete x;
        throw;
    }
    return x;
}

forCe
()
Ответ на: комментарий от eao197

Во второй раз в месте закрывающей фигурной скобки, тем самым точно отмеряя, где именно время жизни unique_ptr завершается.

До момента, когда код дойдет до } с указателем и объектом может много чего произойти, включая передачу владения, смену объекта и пр. Т.е. скобка никак не эквивалентна delete.

forCe
()
Ответ на: комментарий от eao197

поскольку если сравнивать с языками с GC, управлению памятью в C++ нужно уделять значительно больше внимания.

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

Опять же, при использовании языков с GC(таких как java/scala) мне не хватает полноценного RAII и кажется, что я вернулся в старые времена ручного управления ресурсами=)

forCe
()
Ответ на: комментарий от forCe

До момента, когда код дойдет до } с указателем и объектом может много чего произойти, включая передачу владения, смену объекта и пр. Т.е. скобка никак не эквивалентна delete.

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

И еще раз повторю, по поводу предыдущего вашего ответа: ручное управление там в использовании unique_ptr. Да, с unique_ptr ручное управление проще, чем без оного. Но в языке с GC программисту вообще не нужно знать про разницу между A, unique_ptr<A> или shared_ptr<A>.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Но в языке с GC программисту вообще не нужно знать про разницу между A, unique_ptr<A> или shared_ptr<A>

А в C++/CLI нужно, хотя GC есть... И в rust, если gc добавят, нужно будет. В D вроде как нужно знать.

anonymous
()
Ответ на: комментарий от eao197

Но в языке с GC программисту вообще не нужно знать про разницу между A, unique_ptr<A> или shared_ptr<A>

Я бы сказал в языке без возможностей выбора технологии управления памятью. Если нет выбора, то да, знать разницу между вариантами не нужно=)

forCe
()
Ответ на: комментарий от forCe

Опять же, при использовании языков с GC(таких как java/scala) мне не хватает полноценного RAII и кажется, что я вернулся в старые времена ручного управления ресурсами=)

Кстати да, есть идеи, как их нормально объединить? А то я вот думаю над этим, и что-то всё упирается в то, что GC-объекты не дружат с non-GC-объектами и детерминизмом в принципе.

GC-объект можно положить внутрь non-GC-объекта без особых проблем, GC до лампочки. Но если сделать наоборот — здравствуйте, финализаторы.

У всяких try-with-resources/using есть ещё не особо приятная возможность 1) их не использовать вообще, 2) сохранить объект (в непригодном состоянии) после выхода из using-блока.

ilammy ★★★
()
Ответ на: комментарий от ilammy

non-GC-объекта

что значит non-GC объект? WeakMap, например? В js, например, можно это сделать. И какие там препятствия могут быть, непонятно. Видимо, только кривоватый дизайн язычка.

newquestion
() автор топика
Ответ на: комментарий от anonymous

А в C++/CLI нужно, хотя GC есть...

А в C++/CLI есть два типа указателей/ссылок: обычные C++ные, которые не контролируются GC, и .NET-овские, которые под контролем GC. Посему, понятное дело, нужно как-то следить за тем, что создается через обычный new, а не через gcnew.

И в rust, если gc добавят, нужно будет

Если бабушке пришьют что-нибудь, что позволит ей стать дедушкой... Тогда и можно будет поговорить.

В D вроде как нужно знать.

В D нужно знать, как жить при отключенном GC. Хотя смогут пользоваться D без GC далеко не все.

Тем не менее, как все эти аналогии связаны с тем, что в C++ ручное управление памятью?

eao197 ★★★★★
()
Ответ на: комментарий от forCe

Я бы сказал в языке без возможностей выбора технологии управления памятью.

Ну хорошо, в C++ есть выбор такой технологии и вы считаете, что в C++ есть автоматическое управление памятью. Например, вот в таком примере:

A * p;
{
    auto a = make_unique<A>(10, "aaa", 20.2);
    a->foo(10);
    a->bar("asaaa");
    p = a.get(); 
}
p->bar("bang!");
Где будет это самое автоматическое управление?

eao197 ★★★★★
()
Ответ на: комментарий от newquestion

Объекты, которыми владеет не сборщик мусора.

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

Мотивация разделения в том, чтобы прицепить к объектам некоторый деструктор для управления подобными ресурсами, который бы вызывался «сразу же» как объект становится мусором, а не когда закончится доступная память.

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

ilammy ★★★
()
Ответ на: комментарий от ilammy

Какой смысл вообще для этих костылей, что это даст? GC не ждет когда кончиться доступная память, он периодически проходит, в удобное время. Делай нулевую ссылку, и объект будет удален при ближайшем проходе. Что за кейс такой, что тебе приспичило удалять его сразу же? Какая разница?

newquestion
() автор топика
Ответ на: комментарий от eao197

В спп есть понятие автоматической памяти. В стандарте, да. Почитай, откроешь для себя много нового.

anonymous
()
Ответ на: комментарий от anonymous

Давайте сразу цитату. А то ведь вы могли читать книгу, а увидеть не пойми что.

eao197 ★★★★★
()
Ответ на: комментарий от newquestion

Вот допустим есть класс File, который представляет файл. Очевидно, что где-то внутри у этих объектов лежат файловые дескрипторы. Эти объекты владеют этими ресурсами. Файловые дескрипторы нужно открывать и закрывать. Дескрипторов на процесс выдаётся ограниченное количество.

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

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

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

ilammy ★★★
()
Ответ на: комментарий от newquestion

Быдлятина ничтожная, там не GC, там тупо подсчет ссылок.

anonymous
()
Ответ на: комментарий от ilammy

Кстати да, есть идеи, как их нормально объединить? А то я вот думаю над этим, и что-то всё упирается в то, что GC-объекты не дружат с non-GC-объектами и детерминизмом в принципе.

Нормально это объединить, наверное, нельзя.

Видимо, нужны не GC-типы у которых будет деструктор. Такие объекты в GC-кучу класть нельзя, ссылаться из кучи на них лучше только через некий weak_ptr, а сильные ссылки освобождать в некотором аналоге «финализатора»(только генерировать его автоматически) или вообще запретить сильные ссылки из GC кучи. Скорее всего, потребуется иметь неподконтрольную GC кучу. Решение не очень красивое, но я пока другого пути не вижу. А вот таким бы средством пользовался.

GC-объект можно положить внутрь non-GC-объекта без особых проблем, GC до лампочки. Но если сделать наоборот — здравствуйте, финализаторы.

Ну мне вот кажется, что финализаторы нужно вообще убрать. Как максимум, генерировать их автоматически, если есть поля не GC-типов или поля, для которых уже сгенерирован финализатор.

forCe
()
Ответ на: комментарий от eao197

Ну хорошо, в C++ есть выбор такой технологии

Он там шире.

в C++ есть автоматическое управление памятью

Полуавтоматическое=) Причем полуавтоматическое управление ресурсами индифферентно к виду ресурса. Память, файлы, соединения, блокировки и пр. - со всем работаем в едином стиле. Полностью автоматические системы пока приемлемо управляют только памятью.

Где будет это самое автоматическое управление?

Полуавтоматическое. Владельцем ресурса является a, после его уничтожения он освободит ресурс. А баги - это баги. Если я стану использовать boehm gc, то смогу так же испортить себе жизнь, правда чуть сместить указатель нужно будет. Так что вид управления памятью тут ортогонален наличию подобных ошибок. Тут все зависит от языка.

forCe
()
Ответ на: комментарий от forCe

Причем полуавтоматическое управление ресурсами индифферентно к виду ресурса.

Речь идет не о том, что механизм RAII в C++ успешно используется для управления разными типами ресурсов, включая динамическую память. А о том, что даже при этом всем управление динамической памятью все равно в C++ осуществляется вручную. В отличии от языков с GC.

А баги - это баги.

Только вот в языках с GC здесь не будет такого злобного бага, как в C++.

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

eao197 ★★★★★
()
Ответ на: комментарий от eao197

А о том, что даже при этом всем управление динамической памятью все равно в C++ осуществляется вручную.

Не вручную. Я нигде не пишу операции освобождения, я не пытаюсь рассмотреть все ветви исполнения и смотреть, не будет ли у меня утечки ресурса, я не просматриваю каждый return/throw на предмет корректного выхода, я не пытаюсь вставить костыли в духе finally(java до 7) или goto к коду освобождения ресурсов(C) и пр. и пр.

RAII и RC относятся к полуавтоматическому управлению памятью. Отдельного класса управления эти механизмы заслуживают хотя бы потому, что отличаются как от ручного управления, так и от GC достаточно существенно.

В отличии от языков с GC

в языках с GC

в языках с GC

Я бы все таки отделял способ управления памятью от языка. По мне так полноценный язык должен позволять использовать как минимум два(полуавтоматическое и автоматическое).

forCe
()
Ответ на: комментарий от forCe

RAII и RC относятся к полуавтоматическому управлению памятью.

Возможно, кому-то так удобно считать. Но здесь подменяется смысл. RAII и RC отвечают за освобождение памяти. А это только часть проблемы управления ею.

Выше уже был продемонстрирован пример с сохранением во внешней переменной указателя из unique_ptr. RAII обеспечило удаление объекта. Но никак не справилось с появлением повисшего указателя.

И не могло справится.

Поэтому-то ваши с i-rinat взгляды на роль RAII меня и не убеждают. Как было в C++ ручное управление, так и осталось. Хорошо хоть, что упростилось за счет включенных в стандарт средств.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Выше уже был продемонстрирован пример с сохранением во внешней переменной указателя из unique_ptr. RAII обеспечило удаление объекта. Но никак не справилось с появлением повисшего указателя.

Это не имеет отношения к GC. Это демонстрация свойств языка. Я же говорил вам про boehm gc, а вы проигнорировали. Тоже касается и C++/CLI упомянутого выше.

Rust, например, без GC справляется с подобными ошибками.

И не могло справится.

А в Rust справляется :)

А GC может не справиться(например, в С) =) Вы, на мой взгляд, путаете особенности языка и особенности GC.

forCe
()
Последнее исправление: forCe (всего исправлений: 1)
Ответ на: комментарий от forCe

Это не имеет отношения к GC. Это демонстрация свойств языка. Я же говорил вам про boehm gc, а вы проигнорировали.

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

Про boehm gc смысла разговаривать нет, поскольку он не является частью стандарта C++ (в отличии от unique_ptr/shared_ptr). Подключение boehm gc в С++ программу полностью аналогично написанию каких-то своих, заточенных под конкретную задачу инструментов для управления памяти. Под конкретную задачу и конкретный компилятор можно вообще точный GC реализовать. Но это вовсе не будет означать, что из C++ исчезло ручное управление памятью.

Rust, например, без GC справляется с подобными ошибками.

Rust, как и упомянутый выше C++/CLI, не является доказательством тезиса i-rinat о том, что из C++ почти исчезло ручное управление памятью. А именно отсутствие каких-либо внятных обоснований этого тезиса и определяет мое участие в данном споре.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Нормальный GC решает проблемы не только с освобождением памяти, но и с отсутствие таких вещей, как повисшие указатели, повторное освобождение памяти и пр.

Хм. Давайте рассмотрим, каким образом GC это делает? Я вот считаю, что GC такими вещами не занимается, а перечисленные гарантии дает язык.

forCe
()
Ответ на: комментарий от eao197

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

Но ведь Rust, который не имеет GC и не заточен под него, таких проблем не имеет.

из C++ исчезло ручное управление памятью.

из C++ почти исчезло ручное управление памятью. А именно отсутствие каких-либо внятных обоснований этого тезиса и определяет мое участие в данном споре.

Но вы ведь помимо оспаривания этого тезиса много чего наговорили=) И мое участие обусловлено несогласием с некоторыми вашими высказываниями.

forCe
()
Ответ на: комментарий от forCe

Хм. Давайте рассмотрим, каким образом GC это делает? Я вот считаю, что GC такими вещами не занимается, а перечисленные гарантии дает язык.

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

Так же языки с GC вынуждены проводить жесткое различие между ссылочными типами и типами-значениями. Так, скорее всего в языке с GC будет существовать запрет на взятие ссылки на экземпляр типа-значения. Т.е. нельзя будет взять ссылку на примитивный int, а потребуется обернуть его в какой-то ссылочный тип и брать ссылку уже на экземпляр обертки (емнип, это называется boxing/unboxing). Следствием чего является отсутствие такой штуки, как (временные) автоматические экземпляры ссылочных типов. Поэтому нет такой проблемы, как возврат ссылки на временный экземпляр, удаляющийся при выходе из области видимости.

Это принципиально важные вещи, лежащие в фундаменте каждого языка. Поэтому мы и имеем либо нормальные языки с GC, либо нормальные языки без оного.

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

Однако, я не настолько копенгаген в языках программирования, чтобы глубоко углубляться в эту тему.

Но ведь Rust, который не имеет GC и не заточен под него, таких проблем не имеет.

Во-первых, Rust я не знаю в достаточной степени, чтобы всерьез о нем говорить.

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

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

И мое участие обусловлено несогласием с некоторыми вашими высказываниями.

Я старался всего лишь аргументировать свою точку зрения на то, почему в C++ ручное управление памятью. Не нужно мои высказывания проектировать на проблемы GC/не-GC вообще. Для такого обширного разговора я некомпетентен и не смогу его продолжать.

eao197 ★★★★★
()
Ответ на: комментарий от forCe

Скорее всего, потребуется иметь неподконтрольную GC кучу.

Вот да, в принципе сойдёт разделение памяти на несколько зон, типа:

  • GC-куча для объектов без финализаторов/деструкторов, которые потребляют только память. Память освобождается когда удобно сборщику мусора. Циклы, глобальные ссылки и прочий треш-угар разрешён в полном объёме.
  • RC-куча для объектов с деструкторами, которые могут держать и другие ресурсы. Вызов деструктора гарантируется где-то между исчезновением последней ссылки на объект и выделением нового объекта в этой куче. Допустим, даже можно иметь сколько угодно таких куч, каждая под свой тип ресурса.
    С циклами мне очень не нравится эта бредятина с ручной расстановкой слабых ссылок, но единственное вроде как рабочее автоматические решение — вспомогательный сборщик циклов — не сочетается с гарантией своевременного вызова деструкторов. Думаю, можно сделать и исключение для этого случая...
  • Стек, для объектов со строгим scoped lifetime. Уничтожение объекта гарантируется при выходе из скоупа.

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

Ну мне вот кажется, что финализаторы нужно вообще убрать. Как максимум, генерировать их автоматически, если есть поля не GC-типов или поля, для которых уже сгенерирован финализатор.

Мне вот тоже кажется, что финализаторы не взлетели. Во всяких С# и Java рекомендуют их использовать только для того, чтобы на всякий пожарный вызвать close()/dispose(). Но даже так, финализаторы тормозят сборщик мусора, так что нехорошо каждому объекту, реализующему IDisposable, выдавать неявный финализатор, просто потому что кто-то может натупить и не вызвать dispose().

ilammy ★★★
()
Последнее исправление: ilammy (всего исправлений: 1)
Ответ на: комментарий от eao197

при проектировании языка его авторы отталкиваются от наличия или отсутствия GC

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

При наличии GC появление повисших указателей затруднительно просто потому, что если есть живой ненулевой указатель, то объект, на который он указывает, так же будет считаться живым и не будет собран GC

Это зависит от многих условий. Можно сделать так, что не будет считаться. Можно вводить отдельные типы для передачи ссылки из кучи в нативные вызовы. Вроде того же pin_ptr. Разные могут быть решения с разными последствиями=)

Отсюда исчезновение проблемы повисших указателей как класса.

Так же как класс исчезают проблемы с повторным освобождением памяти

Это все следствие не наличия GC, а отсутствия других механизмов=) А это уже свойство языка.

Для такого обширного разговора я некомпетентен и не смогу его продолжать.

Ок. А я бы поговорил.

forCe
()
Ответ на: комментарий от ilammy

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

С циклами мне очень не нравится эта бредятина с ручной расстановкой слабых ссылок, но единственное вроде как рабочее автоматические решение — вспомогательный сборщик циклов — не сочетается с гарантией своевременного вызова деструкторов.

Ну если у нас ручная расстановка weak'ов, то и вызываем деструкторы сразу. Плюс можно сделать отдельный детектор циклов для отладки и/или отложенного удаления(типа «если вы ошиблись с установкой weak, то ресурс будет освобожден тогда, когда детектор циклов обнаружит его»), но в случае отложенного удаления сложнее разобраться с порядком освобождения.

Стек, для объектов со строгим scoped lifetime. Уничтожение объекта гарантируется при выходе из скоупа.

Ну это пересекается с RC, т.к. такие объекты могут быть созданы на стеке и будут освобождать ресурсы независимо от того, в куче они или на стеке.

Интересные моменты — это могут ли объекты перемещаться между этими зонами, и можно ли создавать и хранить ссылки между зонами.

Ну стек и RC между собой без проблем, как сейчас в крестах.

Ссылки из RC/стека на GC:

  • если наш сборщик отслеживает ссылки из RC/стека, то и проблем нет
  • если не отслеживает, то делаем pin_ptr.

Второй вариант больше подходит для нативных языков и интеграции с C.

Ссылки из GC на RC/стек:

  • запрещаем сильные ссылки, толко weak
  • или генерируем финализаторы в случае полей-сильных ссылок/значений типов с деструкторами или полей-ссылок с финализаторами. но ручных финализаторов не делаем

Но в этом случае придется еще на уровне типов отделять тех, кто может сидеть в куче GC и тех, кто нет. Не уверен, что многим это понравится.

forCe
()
Ответ на: комментарий от forCe

Ну если у нас ручная расстановка weak'ов, то и вызываем деструкторы сразу. Плюс можно сделать отдельный детектор циклов для отладки и/или отложенного удаления(типа «если вы ошиблись с установкой weak, то ресурс будет освобожден тогда, когда детектор циклов обнаружит его»), но в случае отложенного удаления сложнее разобраться с порядком освобождения.

Я думаю, нехорошо позиционировать язык как автоматически управляющий ресурсами, но требовать ручной расстановки слабых ссылок, чтобы эта автоматика работала как следует. Прям, «мы всё автоматически делаем, но только если пометить ненужные объекты вызовом free()».

Если разработчик знает, что делает, то он может руками воткнуть слабые ссылки где надо и получить гарантированный вызов деструкторов сразу же. Но если он не знает, то это не должно приводить к перманентным утечкам, которые никак не исправить, потому что добраться до цикла уже нельзя.

Фоновый детектор циклов время от времени может сканировать кучу на предмет недоступных островов объектов, разрывать циклы, и заталкивать теперь мёртвые объекты в очередь на удаление. Правда, пока мне непонятно, как именно он будет догадываться, где разорвать цикл. По идее, деструкторы объектов вполне могут рассчитывать на то, что поля объекта всё ещё ссылаются на то, на что ссылались при жизни. Нехорошо оттуда забирать значения. Ручками поставленные слабые ссылки же как раз и дают такую точку, где код готов к тому, что ссылка может пропасть. Так что может детектор циклов — это не совсем хорошая идея.

Ссылки из RC/стека на GC:

GC и так должен отслеживать ссылки со стека, и может поддерживать специальные ссылки (типа те же weak), так что ещё один тип «ссылка из RC-кучи» по идее погоды не сделает. Однако, это ж значит, что 1) GC теперь должен будет просматривать ещё и части RC-кучи, где есть/были ссылки на GC-кучу, 2) деструкторы RC-объектов должны будут говорить GC, что здесь ссылок больше нет. Так что пиннинг может быть лучше, если такие ссылки редкие.

Ссылки из GC на RC/стек:

Здесь мне больше нравится идея только слабых ссылок. Тогда GC-куча — это куча для объектов, которые владеют только памятью, но не другими ресурсами. Для других ресурсов нужна сильная ссылка на RC-объект, который владеет ресурсом.

Ненужные GC-объекты можно освобождать когда угодно. Им гарантированно не нужны финализаторы. Они не могут воскреснуть в непонятном состоянии. Они не смогут продолжать держать RC-объекты живыми неопределённое время из-за какого-нибудь generational GC, который до них не доходит уже двадцатый цикл сборки подряд, потому что ему везёт.

Но в этом случае придется еще на уровне типов отделять тех, кто может сидеть в куче GC и тех, кто нет.

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

Кстати, насчёт счётчиков ссылок. Их бы тоже неплохо как в Rust разделить на Rc и Arc, чтобы не дёргать атомарные операции почём зря. Но это разделение за собой тащит ещё и хотелку безопасной работы с потоками, которая вообще ортогональна управлению памятью и ресурсами.

ilammy ★★★
()
Ответ на: комментарий от ilammy

нехорошо позиционировать язык как автоматически управляющий ресурсами, но требовать ручной расстановки слабых ссылок

Нужно не требовать, а позволять. Так же опционально предоставить детектор циклов с выбором стратегии их обработки.

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

Можно дать выбор стратегии. Самый корректный но медленный вариант может все делать сам, например, анализируя некую метаинформацию, оставленную компилятором при формировании деструктора. Но тут нужно думать=)

Так что пиннинг может быть лучше, если такие ссылки редкие.

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

Вот тут да, придётся об этом всё же думать пользователю.

Это много кому может не понравится=)

forCe
()
Ответ на: комментарий от forCe

Это много кому может не понравится=)

Это уже даже мне не нравится. Достаточно вспомнить об интерфейсах (или любом другом способе абстракции от типов).

ilammy ★★★
()
Ответ на: комментарий от ilammy

А есть какие-то мысли на счет альтернативных вариантов?

Возможно, вывод регионов был бы неплохим механизмом. Но насколько я знаю, там пока все не далеко продвинулось.

forCe
()

В таком случае, у нас разница между языком с GC и без него только в том, что нет накладных расходов на сам GC.

Самурай без меча подобен самураю с мечом, но только без меча.

Непосредственно на расход памяти это не влияет, практически, так получается?

АХАХАХАХАХА. Ты думаешь, что информация, о количестве ссылок или доступности ветки памяти на магии работает?

Aswed ★★★★★
()
Ответ на: комментарий от forCe

Где я тут памятью вручную управлял?

Ты правда слепой или решил под дурачка закосить? Вот в этой строке:

auto a = make_unique<A>(10, «aaa», 20.2);

А затем в следующих строках ты писал

a->method

То, что компилятор вписал вызов деструктора на выходе из области видимости a — это не более чем сахарок, хоть и весьма приятный временами.

kawaii_neko ★★★★
()
Ответ на: комментарий от anonymous

Но ведь если ты не «занулишь ссылочку в словаре» и словарь будет жить, то и с GC ты получишь утечку

Действительно. Но GC при этом не обременяет программиста костылями в виде weak references.

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

это не более чем сахарок

Это не может быть сахаром по определению синтаксического сахара.

Вот в этой строке

Там нет управления памятью.

anonymous
()
Ответ на: комментарий от kawaii_neko

Но GC при этом не обременяет программиста костылями в виде weak references.

Обременяет. По крайней мере weak reference есть в JVM, .NET, Ruby, Python и т.д.

anonymous
()
Ответ на: комментарий от kawaii_neko

Действительно. Но GC при этом не обременяет программиста костылями в виде weak references.

Если бы ты писал что-то сложное, то знал бы, что это не костыли, и что они нужны при работе GC.

anonymous
()
Ответ на: комментарий от kawaii_neko

Где я тут памятью вручную управлял?

Ты правда слепой или решил под дурачка закосить?

Вот ты умный, сформулируй - что такое ручное управление памятью? А то некоторые считают, что make_unique с присваивание unique_ptr - это уже нифига не ручное управление.

tailgunner ★★★★★
()
Ответ на: комментарий от i-rinat

А вместо конструкторов фабрики надо использовать?

Если брать плюсовые аналогии, то talloc — это аллокатор, у которого запрашивается N байт, внутри которых происходит «placement new». Никаких фабрик.

kawaii_neko ★★★★
()
Ответ на: комментарий от anonymous

Если бы ты писал что-то сложное, то знал бы, что это не костыли, и что они нужны при работе GC.

Кхм, GC умеет обнаруживать и грохать циклические ссылки. Можешь дать пример, когда weak pointers нужны?

kawaii_neko ★★★★
()
Ответ на: комментарий от anonymous

Обременяет. По крайней мере weak reference есть в JVM, .NET, Ruby, Python и т.д.

Просто чтобы ты знал, в python и ruby нет gc. Там счетчики ссылок и уничтожение объектов по достижении нуля refcounter-ом.

А в JVM и .NET есть reflection, позволяющий обходить инкапсуляцию и вызывать приватные методы любого класса, но это не значит, что так на самом деле стоит поступать.

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Просто чтобы ты знал, в python и ruby нет gc. Там счетчики ссылок и уничтожение объектов по достижении нуля refcounter-ом.

рукалицо.жипег

anonymous
()
Ответ на: комментарий от i-rinat

Писать на C++ (и тем более на C!) еще более немодно, чем не использовать фабрики, синглтоны, делегаты, визиторы, фасады и прочие индусские методики.

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Просто чтобы ты знал, в python и ruby нет gc. Там счетчики ссылок и уничтожение объектов по достижении нуля refcounter-ом.

А так же там есть детектор циклов и их удаление.

но это не значит, что так на самом деле стоит поступать.

Не значит. Но в JVM/.NET WeakReference есть. И это при том, что там одни из лучших сборщиков.

anonymous
()
Ответ на: комментарий от kawaii_neko

Кхм, GC умеет обнаруживать и грохать циклические ссылки. Можешь дать пример, когда weak pointers нужны?

Ну, как правило, weak'и в случае GC используют, чтобы без проблем подписывать объекты, класть их в какие-то долгоживущие списки, для кэширования и т.д. и т.п.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.