Предполагается, что в таких языках, как C, программист вставляет вызовы бесплатно. Почему компилятор не делает это автоматически? Люди делают это в разумные сроки (игнорируя ошибки), поэтому это не невозможно.
РЕДАКТИРОВАТЬ: Для дальнейшего использования, вот еще одна дискуссия, которая имеет интересный пример.
compilers
memory-management
garbage-collection
Милтон Сильва
источник
источник
Ответы:
Потому что неясно, будет ли программа снова использовать память. Это означает, что ни один алгоритм не может правильно определить, когда вызывать
free()
во всех случаях, что означает, что любой компилятор, который пытался это сделать, обязательно генерировал бы некоторые программы с утечками памяти и / или некоторые программы, которые продолжали использовать освобожденную память. Даже если вы обеспечите, чтобы ваш компилятор никогда не делал второй, и позволили программисту вставлять вызовы дляfree()
исправления этих ошибок, знать, когда вызыватьfree()
этот компилятор, будет даже сложнее, чем знать, когда вызыватьfree()
при использовании компилятора, который не пытался помогать.источник
free()
правильно.Как справедливо заметил Дэвид Ричерби, проблема в целом неразрешима. Жизнеспособность объекта является глобальным свойством программы и может в целом зависеть от входных данных для программы.
Даже точная динамическая сборка мусора - неразрешимая проблема! Все реальные сборщики мусора используют достижимость как консервативное приближение к тому, понадобится ли выделенный объект в будущем. Это хорошее приближение, но, тем не менее, это приближение.
Но это только правда в целом. Один из самых печально известных отговорок в компьютерном бизнесе - «это вообще невозможно, поэтому мы ничего не можем сделать». Наоборот, есть много случаев, когда можно добиться прогресса.
Реализации, основанные на подсчете ссылок, очень близки к «освобождению компилятора», так что трудно заметить разницу. LLVM «ы автоматический подсчет ссылок (используется в Objective-C и Swift ) является известным примером.
Вывод области и сборка мусора во время компиляции - текущие активные области исследования. Оказывается, намного проще в декларативных языках, таких как ML и Mercury , где вы не можете изменить объект после его создания.
Теперь, что касается людей, есть три основных способа, которыми люди управляют временем жизни выделения вручную:
источник
Это проблема неполноты, а не неразрешимости
Хотя верно то, что оптимальное размещение операторов освобождения неразрешимо, здесь проблема не в этом. Так как это неразрешимо как для людей, так и для компиляторов, невозможно всегда сознательно выбирать оптимальное размещение освобождения, независимо от того, является ли это ручным или автоматическим процессом. И поскольку никто не совершенен, достаточно продвинутый компилятор должен быть в состоянии превзойти людей в угадывании приблизительно оптимальных мест размещения. Итак, неразрешимость - это не то, почему нам нужны явные операторы освобождения .
Существуют случаи, когда внешние знания информируют о размещении выписки по освобождению. Удаление этих утверждений равносильно удалению части операционной логики, а запрос компилятора на автоматическую генерацию этой логики равносильно тому, чтобы он угадал, что вы думаете.
Например, предположим, что вы пишете Read-Evaluate-Print-Loop (REPL) : пользователь вводит команду, а ваша программа выполняет ее. Пользователь может выделить / освободить память, введя команды в ваш REPL. Ваш исходный код будет указывать, что REPL должен делать для каждой возможной пользовательской команды, включая освобождение, когда пользователь вводит команду для нее.
Но если в исходном коде C нет явной команды для освобождения, то компилятору потребуется сделать вывод, что он должен выполнить делокацию, когда пользователь вводит соответствующую команду в REPL. Эта команда «освобождает», «освобождает» или что-то еще? Компилятор не может знать, какой командой вы хотите быть. Даже если вы программируете в логике, чтобы искать это командное слово, и REPL находит его, компилятор не может знать, что он должен ответить на него освобождением, если вы явно не указали это в исходном коде.
tl; dr Проблема в том, что исходный код на C не предоставляет компилятору внешние знания. Неразрешимость не является проблемой, потому что она существует независимо от того, является ли этот процесс ручным или автоматическим.
источник
В настоящее время ни один из опубликованных ответов не является полностью правильным.
Некоторые делают. (Я объясню позже.)
Тривиально, вы можете позвонить
free()
перед выходом из программы. Но в вашем вопросе подразумевается необходимость звонитьfree()
как можно скорее.Проблема того, когда вызывать
free()
в любой C-программе, как только память недоступна, неразрешима, т. Е. Для любого алгоритма, предоставляющего ответ за конечное время, есть случай, который он не охватывает. Это - и многие другие неразрешимости произвольных программ - могут быть доказаны из проблемы останова .Неразрешимая проблема не всегда может быть решена за конечное время любым алгоритмом, будь то компилятор или человек.
Люди (пытаются) написать в подмножестве программ на С, которые могут быть проверены на правильность памяти их алгоритмом (сами).
Некоторые языки достигают # 1, встроив # 5 в компилятор. Они не позволяют программам с произвольным использованием распределения памяти, а скорее разрешимым подмножеством их. Foth и Rust - два примера языков, которые имеют более ограниченное распределение памяти, чем C
malloc()
, которые могут (1) обнаружить, написана ли программа вне их разрешимого набора (2), автоматически вставлять освобождения.источник
«Люди делают это, так что это не невозможно» - это общеизвестная ошибка. Мы не обязательно понимаем (не говоря уже о контроле) вещи, которые мы создаем - деньги - типичный пример. Мы склонны переоценивать (иногда резко) наши шансы на успех в технических вопросах, особенно когда кажется, что человеческий фактор отсутствует.
Человеческие способности в компьютерном программировании очень плохие , а изучение компьютерных наук (не во многих программах профессионального образования) помогает понять, почему эту проблему не так просто решить. Мы можем когда-нибудь, возможно, не слишком далеко, быть заменены искусственным интеллектом на работе. Даже в этом случае не будет общего алгоритма, который бы всегда правильно выполнял освобождение.
источник
Отсутствие автоматического управления памятью является особенностью языка.
С не должен быть инструментом для написания программного обеспечения легко. Это инструмент, заставляющий компьютер делать то, что вы говорите. Это включает в себя выделение и освобождение памяти в момент вашего выбора. C - это низкоуровневый язык, который вы используете, когда хотите точно управлять компьютером или когда вы хотите делать что-то иначе, чем ожидали разработчики языка / стандартной библиотеки.
источник
Проблема в основном исторический артефакт, а не невозможность реализации.
Большинство компиляторов Си создают код так, что компилятор видит только каждый исходный файл одновременно; он никогда не видит всю программу сразу. Когда один исходный файл вызывает функцию из другого исходного файла или библиотеки, все, что видит компилятор, - это файл заголовка с типом возвращаемого значения функции, а не фактическим кодом функции. Это означает, что когда есть функция, которая возвращает указатель, компилятор не может сказать, нужно ли освобождать память, на которую указывает указатель, или нет. Информация для принятия решения, которая не отображается компилятору в этот момент времени. С другой стороны, человек-программист может свободно искать исходный код функции или документацию, чтобы выяснить, что нужно сделать с указателем.
Если вы посмотрите на более современные низкоуровневые языки, такие как C ++ 11 или Rust, вы обнаружите, что они в основном решили проблему, сделав явное владение памятью в типе указателя. В C ++ вы должны использовать
unique_ptr<T>
вместо обычногоT*
для хранения памяти, и онunique_ptr<T>
гарантирует, что память освобождается, когда объект достигает конца области, в отличие от простогоT*
. Программист может передавать память от одногоunique_ptr<T>
к другому, но может быть только один,unique_ptr<T>
указывающий на память. Так что всегда понятно, кому принадлежит память и когда ее нужно освободить.C ++ по причинам обратной совместимости все еще допускает ручное управление памятью старого стиля и, таким образом, создание ошибок или способов обойти защиту
unique_ptr<T>
. Rust еще более строг в том смысле, что он обеспечивает соблюдение правил владения памятью через ошибки компилятора.Что касается неразрешимости, проблемы остановки и тому подобного, да, если вы придерживаетесь семантики C, для всех программ невозможно решить, когда освободить память. Однако для большинства актуальных программ, а не для академических упражнений или программного обеспечения с ошибками, абсолютно возможно решить, когда освободить, а когда нет. В конце концов, это единственная причина, по которой человек может понять, освободить его или нет.
источник
Другие ответы были сосредоточены на том, возможно ли выполнить сборку мусора, какие-то подробности о том, как это делается, и некоторых проблемах.
Одной из проблем, которая еще не была рассмотрена, является неизбежная задержка сбора мусора. В C, когда программист вызывает free (), эта память сразу же доступна для повторного использования. (По крайней мере, в теории!) Таким образом, программист может освободить свою структуру размером 100 МБ, выделить еще одну структуру размером 100 МБ спустя миллисекунду и ожидать, что общее использование памяти останется прежним.
Это не так с сборкой мусора. Системы сбора мусора имеют некоторую задержку при возврате неиспользуемой памяти в кучу, и это может быть значительным. Если ваша структура размером 100 МБ выходит из области видимости, и спустя миллисекунду ваша программа устанавливает другую структуру размером 100 МБ, вы можете ожидать, что ваша система будет использовать 200 МБ в течение короткого периода времени. Этот «короткий период» может составлять миллисекунды или секунды, в зависимости от системы, но все равно есть задержка.
Если вы работаете на ПК с гигабайтами оперативной памяти и виртуальной памяти, вы, вероятно, никогда этого не заметите. Если вы работаете в системе с более ограниченными ресурсами (например, встроенной системой или телефоном), к этому нужно относиться серьезно. Это не просто теоретически - я лично видел, что это создает проблемы (например, при сбое устройства) при работе в системе WinCE с использованием .NET Compact Framework и разработке на C #.
источник
Вопрос предполагает, что освобождение - это то, что программист должен выводить из других частей исходного кода. Это не. «На данный момент в программе ссылка на память FOO больше не является полезной» - это информация, известная только в уме программиста, пока она не закодирована в (на процедурных языках) оператор освобождения.
Это теоретически не отличается от любой другой строки кода. Почему компиляторы не вставляют автоматически «В этой точке программы, проверьте регистр BAR для ввода» или «если вызов функции возвращает ненулевое значение, выйдите из текущей подпрограммы» ? С точки зрения компилятора причина - «неполнота», как показано в этом ответе . Но любая программа страдает от незавершенности, когда программист не рассказал ей все, что он знает.
В реальной жизни освобождение - это грубая работа или шаблон; наш мозг заполняет их автоматически и ворчит по этому поводу, и мнение «компилятор может сделать это так же хорошо или лучше» - это правда. В теории, однако, это не так, хотя, к счастью, другие языки дают нам больший выбор теории.
источник
Что будет сделано: Существует мусор, и есть компиляторы с помощью подсчета ссылок (Objective-C, Swift). Те, кто выполняет подсчет ссылок, нуждаются в помощи программиста, избегая сильных циклов ссылок.
Реальный ответ на «почему» является то , что компилятор авторы не выяснили способ , который достаточно хорошо и достаточно быстро , чтобы сделать его пригодным для использования в компиляторе. Поскольку авторы компиляторов обычно достаточно умны, вы можете заключить, что очень и очень трудно найти достаточно хороший и быстрый способ.
Одна из причин того, что это очень, очень трудно, конечно, что это неразрешимо. В информатике, когда мы говорим о «разрешимости», мы имеем в виду «принятие правильного решения». Конечно, программисты-люди могут легко решить, где освободить память, потому что они не ограничены правильными решениями. И они часто принимают неправильные решения.
источник
Потому что время жизни блока памяти - это решение программиста, а не компилятора.
Вот и все. Это конструкция C. Компилятор не может знать, каково было намерение выделить блок памяти. Люди могут сделать это, потому что они знают цель каждого блока памяти и когда эта цель достигается, чтобы ее можно было освободить. Это часть дизайна написанной программы.
C - это язык низкого уровня, поэтому случаи передачи блока вашей памяти другому процессу или даже другому процессору довольно часты. В крайнем случае, программист может намеренно выделить часть памяти и никогда не использовать ее снова только для того, чтобы оказать давление на память в других частях системы. Компилятор не может знать, нужен ли этот блок.
источник
В Си и многих других языках действительно есть возможность заставить компилятор делать эквивалент этого в тех случаях, когда во время компиляции ясно, когда это должно быть сделано: использование переменных с автоматической продолжительностью (т. Е. Обычных локальных переменных) , Компилятор отвечает за организацию достаточного пространства для таких переменных и за освобождение этого пространства, когда заканчивается их (четко определенное) время жизни.
Поскольку массивы переменной длины являются функцией C, начиная с C99, объекты с автоматической продолжительностью, в принципе, обслуживают практически все функции в C, которые выполняют динамически распределяемые объекты вычислимой продолжительности. На практике, конечно, реализации C могут накладывать значительные практические ограничения на использование VLA - то есть их размер может быть ограничен в результате размещения в стеке - но это соображение реализации, а не соображение проектирования языка.
Те объекты, использование которых по назначению не дает им автоматической продолжительности, - это именно те объекты, срок жизни которых нельзя определить во время компиляции.
источник