Якщо мені потрібно використовувати частину пам'яті протягом усього часу моєї програми, чи дійсно потрібно звільнити її безпосередньо перед завершенням програми?


67

У багатьох книгах та навчальних посібниках я чув, як підкреслювалась практика управління пам’яттю, і я відчував, що деякі таємничі та страшні речі трапляться, якщо я не звільнить пам’ять після того, як закінчую її використання.

Я не можу говорити про інші системи (хоча мені доцільно припустити, що вони застосовують подібну практику), але принаймні в Windows, ядро ​​в основному гарантовано очищує більшість ресурсів (за винятком непарних кількох), якими користується програма після закінчення програми. До якої належить купа пам’яті, серед іншого.

Я розумію, чому ви хочете закрити файл після того, як ви закінчите його використання, щоб зробити його доступним для користувача або чому ви хочете відключити розетку, підключену до сервера, щоб зберегти пропускну здатність, але це здається нерозумним повинні мікроконтролювати ВСІ вашу пам'ять, яку використовує ваша програма.

Тепер я погоджуюся з тим, що це питання є широким, оскільки те, як ви маєте поводитися зі своєю пам'яттю, ґрунтується на тому, скільки пам’яті вам потрібно, і коли вона вам потрібна, тому я звужу сферу цього питання до цього: Якщо мені потрібно використовувати шматок пам'ять протягом усього життя моєї програми, чи дійсно потрібно звільнити її безпосередньо перед припиненням програми?

Редагувати: Запропонований як дублікат питання стосувався сімейства операційних систем Unix. Його головна відповідь навіть вказала інструмент, характерний для Linux (наприклад, Valgrind). Це питання має на меті охопити більшість "звичайних" невбудованих операційних систем, і чому це - чи не є хорошою практикою звільнення пам'яті, яка потрібна протягом усього життя програми.



19
Ви позначили цей агностик мовою, але для багатьох мов (наприклад, Java) взагалі не потрібно звільняти пам'ять вручну; це відбувається автоматично через деякий час після того, як остання посилання на об'єкт виходить із сфери застосування
Річард Тінгл

3
і звичайно, ви можете писати C ++ без жодного видалення, і це буде добре для пам'яті
Nikko

5
@RichardTingle Хоча я не можу придумати будь-яку іншу мову вгорі голови, крім C та, можливо, C ++, тег мови-агностики мав на меті охопити всі мови, у яких не вбудовано багато утиліт збору сміття. Однак це, мабуть, рідко. Ви можете стверджувати, що тепер ви можете впровадити таку систему в C ++, але все-таки у вас є вибір не видаляти частину пам'яті.
КапітанОчевидний

4
Ви пишете: "Ядро в основному гарантовано очищає всі ресурси [...] після закінчення програми". Це, як правило, помилково, оскільки не все - це пам'ять, ручка чи об’єкт ядра. Але це правда для пам’яті, до якої ви негайно обмежуєте своє запитання.
Ерік Тауерс

Відповіді:


108

Якщо мені потрібно використовувати частину пам'яті протягом усього часу моєї програми, чи дійсно потрібно звільнити її безпосередньо перед завершенням програми?

Це не є обов’язковим, але може мати переваги (а також деякі недоліки).

Якщо програма виділяє пам'ять один раз за час її виконання, і в іншому випадку ніколи не звільнятиме її до завершення процесу, може бути розумним підходом не випускати пам'ять вручну і не покладатися на ОС. У будь-якій сучасній ОС, яку я знаю, це безпечно, в кінці процесу вся виділена пам'ять надійно повертається в систему.
У деяких випадках очищення виділеної пам’яті явно може бути значно швидшим, ніж очищення.

Однак, звільнивши всю пам'ять в кінці виконання явно,

  • під час налагодження / тестування інструменти виявлення витоку пам’яті не показуватимуть «помилкові позитиви»
  • може бути набагато простіше перенести код, який використовує пам'ять разом з розподілом і розподілом, в окремий компонент і використовувати його пізніше в іншому контексті, коли час використання пам'яті потрібно контролювати користувачем компонента

Тривалість життя програм може змінюватися. Можливо, у вашій програмі сьогодні є невелика утиліта командного рядка, типова тривалість життя якої становить менше 10 хвилин, і вона виділяє пам'ять частинами по кілька кб кожні 10 секунд - тому не потрібно звільняти виділену пам'ять взагалі до закінчення програми. Пізніше програма змінюється і отримує розширене використання як частина серверного процесу з терміном життя в кілька тижнів - тому звільнення невикористаної пам’яті між ними вже не є варіантом, інакше програма починає з'їдати всю наявну пам’ять сервера з часом . Це означає, що вам доведеться переглянути всю програму і потім додати код розмови. Якщо вам пощастило, це легке завдання, якщо ні, можливо, це настільки важко, що шанси високі, що ви пропустите місце. І коли ви опинитесь у цій ситуації, ви хочете, щоб ви додали "безкоштовно"

Більш загально, написання розподільного та пов'язаного з ним коду взаєморозподілу завжди попарно вважається "хорошою звичкою" серед багатьох програмістів: роблячи це завжди, ви зменшуєте ймовірність забути код делокації в ситуаціях, коли пам'ять повинна бути звільнена.


12
Гарний момент щодо розвитку хороших навичок програмування.
Лоуренс

4
Як правило, я дуже погоджуюся з цією порадою. Збереження гарних звичок з пам’яттю дуже важливо. Однак, я думаю, є винятки. Наприклад, якщо ви виділили якийсь божевільний графік, на який буде потрібно кілька секунд, щоб пройти та вільно "належним чином", то ви б покращили користувацький досвід, просто закінчивши програму та дозволивши ОС запустити її.
GrandOpener

1
Це безпечно для кожної сучасної "нормальної" (не вбудованої із захистом пам’яті) ОС, і це було б серйозною помилкою, якби не вона. Якщо непривілейований процес може назавжди втратити пам'ять, яку ОС не відшкодує, то запустивши її багато разів, система може запустити пам'ять. Довгострокове здоров'я ОС не може залежати від відсутності помилок у непривілейованих програмах. (звичайно, це ігнорування таких речей, як сегменти спільної пам’яті Unix, які в кращому випадку підтримуються простором обміну.)
Пітер Кордес

7
@GrandOpener У цьому випадку ви можете скористатись для цього дерева певним алокатором на основі регіону, щоб ви могли виділити його звичайним способом і просто розмістити весь регіон одразу, коли настане час, замість того, щоб пройти його та звільнити. це потроху Це все ще "належно".
Томас

3
Крім того, якщо пам'ять дійсно повинна існувати протягом усього життя програми, може бути розумною альтернативою створення структури на стеку, наприклад, в main.
Кайл Странд

11

Звільнення пам’яті наприкінці запуску програм - це лише витрата часу на процесор. Це як прибирати будинок, перш ніж вивести його з орбіти.

Однак іноді те, що було короткою програмою, може перетворитись на частину значно тривалішої програми. Тоді звільнення речі стає необхідним. Якщо це не задумувалося хоч якоюсь мірою, то це може включати значну переробку.

Одне розумне рішення цього питання - це "толк", який дозволяє вам завантажувати пам'ять, а потім викидати їх за один дзвінок.


1
Якщо припустити, що ви знаєте, що ваша ОС не має своїх витоків.
WGroleau

5
"втрата часу процесора" Хоча дуже і дуже мало часу. freeяк правило , набагато швидше , ніж malloc.
Пол Дрейпер

@PaulDraper Так, витрата часу, обмінюючи все назад, набагато важливіша.
Дедуплікатор

5

Ви можете використовувати мову зі збиранням сміття (наприклад, Scheme, Ocaml, Haskell, Common Lisp і навіть Java, Scala, Clojure).

(В більшості мов GC-й вид, немає ніякого способу , щоб явно і вручну вільна пам'ять Іноді, деякі значення можуть бути! Завершено , наприклад, GC і виконання система закриє значення дескриптора файлу , коли це значення недосяжно, але це не впевнений, ненадійний, і вам слід замість цього явно закрити свої файли, оскільки остаточне завершення не гарантується)

Ви також можете застосувати для своєї програми, кодованої в C (або навіть C ++) , консервативний сміттєзбірник Boehm . Тоді ви заміните всі свої mallocна GC_malloc і не freeпереймаєтесь будь-яким вказівником. Звичайно, вам потрібно зрозуміти плюси і мінуси використання GC Boehm. Прочитайте також підручник з GC .

Управління пам'яттю - глобальна властивість програми. У деякому роді це (і життєздатність деяких даних) є некомпозиційним та немодульним, оскільки це властивість програми.

Нарешті, як зазначали інші, чітке використання freeвашої виділеної на пам'яті зони пам’яті C - це добра практика. Якщо іграшкова програма не виділяє багато пам’яті, ви навіть можете вирішити взагалі не freeпам’ятати (оскільки коли процес закінчиться, його ресурси, включаючи віртуальний адресний простір , буде звільнена операційною системою).

Якщо мені потрібно використовувати частину пам'яті протягом усього часу моєї програми, чи дійсно потрібно звільнити її безпосередньо перед завершенням програми?

Ні, вам не потрібно цього робити. І багато реальних програм не турбуються, щоб звільнити деяку пам'ять, яка потрібна протягом усього життя (зокрема, компілятор GCC не звільняє частину своєї пам'яті). Однак коли ви це зробите (наприклад, ви не турбуєтесь - freeвводячи якийсь певний фрагмент динамічно виділених даних C ), ви краще прокоментуйте цей факт, щоб полегшити роботу майбутніх програмістів над тим самим проектом. Я схильний рекомендувати, щоб обсяг незатребуваної пам'яті залишався обмеженим, і, як правило, відносно невеликим WT до загальної кількості використовуваної купи пам'яті.

Зауважте, що система freeчасто не випускає пам'ять в ОС (наприклад, викликаючи munmap (2) в системах POSIX), але зазвичай позначає зону пам'яті як багаторазову для використання в майбутньому malloc. Зокрема, віртуальний адресний простір (наприклад, як це було показано через /proc/self/mapsLinux, див. Proc (5) ....) після цього може не скорочуватися free(отже, утиліти на зразок psабо topповідомляють про той самий об'єм використовуваної пам'яті для вашого процесу).


3
"Іноді деякі значення можуть бути доопрацьовані, наприклад, GC та система виконання роботи закриють значення обробки файлу, коли це значення недосяжне" Ви можете підкреслити, що в більшості мов GCed немає гарантії, що пам'ять ніколи не буде повернена, а також будь-який фіналізатор, коли-небудь запускається, навіть при виході: stackoverflow.com/questions/7880569/…
Deduplicator

Я особисто віддаю перевагу цій відповіді, оскільки вона заохочує нас до використання GC (тобто більш автоматизованого підходу).
Та Тхань Дінь

3
@tathanhdinh Більш автоматизована, хоча виключно для пам'яті. Повністю керівництво для всіх інших ресурсів. GC працює, торгуючи детермінізмом і пам'яттю для зручного управління пам'яттю. І ні, фіналізатори не дуже допомагають і мають свої проблеми.
Дедуплікатор

3

Це не обов'язково , так як у вас не вийде правильно виконати свою програму, якщо цього не зробите. Однак є причини, які ви можете вибрати, якщо вам нададуть шанс.

Один з найпотужніших випадків, з якими я стикаюся (знову і знову), - це те, що хтось пише невеликий фрагмент коду імітації, який працює у їх виконанні. Вони кажуть: "Ми хотіли б, щоб цей код був інтегрований в моделювання". Тоді я запитую їх, як вони планують реініціалізуватись між біжками Монте-Карло, і вони спокійно дивляться на мене. "Що означає повторна ініціалізація? Ви просто запускаєте програму з новими налаштуваннями?"

Іноді чисте очищення значно полегшує використання програмного забезпечення. У випадку багатьох прикладів, ви припускаєте, що вам ніколи не потрібно щось очищати, і ви робите припущення про те, як можна обробляти дані та їх тривалість життя навколо цих припущень. Коли ви переходите до нового середовища, де ці припущення не дійсні, цілі алгоритми можуть більше не працювати.

Для прикладу того, як дивні речі можуть вийти, подивіться, як керовані мови справляються з доопрацюванням наприкінці процесів, або як C # має справу з програмною зупинкою Доменів додатків. Вони зав'язують себе вузлами, тому що в цих крайніх випадках є припущення, які потрапляють через щілини.


1

Ігнорування мов, де ви все одно не звільняєте пам'ять вручну ...

Те, що ви зараз вважаєте "програмою", в певний момент може стати лише функцією або методом, який є частиною більшої програми. І тоді цю функцію можна буде викликати кілька разів. І тоді пам'ять, яку ви мали б "звільнити вручну", буде витоком пам'яті. Це, звичайно, судовий заклик.


2
це, здається, просто повторює пункт, зроблений (і набагато краще пояснений) у верхній відповіді , опублікованій за кілька годин до цього: "може бути набагато простіше перемістити код, який використовує пам'ять разом з розподілом і розстановкою, в окремий компонент і використовувати його пізніше в іншому контексті, коли час використання пам'яті потрібно контролювати користувачем компонента ... "
gnat

1

Оскільки дуже ймовірно, що через деякий час ви захочете змінити свою програму, можливо, інтегрувати її з чимось іншим, запустити кілька примірників послідовно або паралельно. Тоді буде потрібно вручну звільнити цю пам'ять - але ви вже не будете пам’ятати обставини, і це коштуватиме набагато більше часу, щоб переосмислити свою програму.

Робіть справи, поки ваше розуміння щодо них ще свіже.

Це невелика інвестиція, яка може принести велику віддачу в майбутньому.


2
це, мабуть, не додає нічого суттєвого щодо висловлених питань та пояснень у попередніх 11 відповідях. Зокрема, пункт про можливу зміну програми в майбутньому був зроблений уже три-чотири рази
gnat

1
@gnat Скрученим і затемненим способом - так. У чіткій заяві - ні. Давайте зосередимось на якості відповіді, а не на швидкості.
Agent_L

1
якість, мудра, найкраща відповідь виявляється набагато краще в поясненні цього пункту
gnat

1

Ні, це не потрібно, але це гарна ідея.

Ви кажете, що відчуваєте, що "таємничі та страшні речі відбудуться, якби я не звільнив пам'ять після того, як закінчую її використання".

У технічному плані єдиним наслідком цього є те, що ваша програма буде їсти більше пам’яті до тих пір, поки не буде досягнуто жорсткого обмеження (наприклад, ваш віртуальний адресний простір вичерпано) або продуктивність не стане неприйнятною. Якщо програма збирається завершити роботу, нічого з цього не має значення, оскільки процес фактично припиняє своє існування. "Загадкові та страшні речі" суто стосуються психічного стану розробника. Пошук джерела витоку пам’яті може бути абсолютним кошмаром (це заниження), і для написання коду, що не протікає, потрібно багато навичок і дисципліни. Рекомендований підхід до розвитку цієї навички та дисципліни - це завжди звільняти пам'ять, коли вона більше не потрібна, навіть якщо програма вже закінчиться.

Звичайно, це має додаткову перевагу в тому, що ваш код можна повторно використовувати та адаптувати легше, як говорили інші.

Однак є принаймні один випадок, коли краще не звільняти пам’ять безпосередньо перед завершенням програми.

Розглянемо випадок, коли ви зробили мільйони невеликих асигнувань, і вони в основному були замінені на диск. Коли ви починаєте звільняти все, більшість вашої пам’яті потрібно повернути назад в оперативну пам’ять, щоб мати доступ до інформації з бухгалтерського обліку, лише негайно відкинути дані. Це може змусити програму зайняти кілька хвилин просто для виходу! Не кажучи вже про тиск на диск і фізичну пам'ять за той час. Якщо фізичної пам’яті бракує для початку (можливо, програма закрита, оскільки інша програма розжовує багато пам’яті), то, можливо, потрібно буде кілька разів замінювати окремі сторінки, коли потрібно звільнити кілька об’єктів ту ж сторінку, але не звільняються послідовно.

Якщо ж замість цього програма просто перестає працювати, ОС просто відкине всю пам’ять, яку було замінено на диск, що майже миттєво, оскільки вона взагалі не потребує доступу до диска.

Важливо зауважити, що в мові ОО, виклик деструктора об'єкта також змусить замінити пам'ять; якщо вам доведеться це зробити, ви можете також звільнити пам'ять.


1
Чи можете ви навести щось, що підтверджує думку про те, що пам'ять, яку потрібно випустити на екран, потрібно передати на сторінку, щоб розібратись? Це, очевидно, неефективно, безумовно, сучасні ОС є розумнішими від цього!

1
@Jon of All Trades, сучасні ОС є розумними, але, як не дивно, більшість сучасних мов це не так. Коли ви звільниться (щось), компілятор помістить "щось" у стек, а потім зателефонує безкоштовно (). Якщо помістити його в стек, потрібно поміняти його назад в оперативну пам’ять, якщо він замінений.
Jeffiekins

@JonofAllTrades вільний список мав би такий ефект, але це досить застаріла реалізація malloc. Але ОС не може завадити вам використовувати таку реалізацію.

0

Основні причини очищення вручну: ви менше шансів помилитися з справжньою витоком пам’яті для блоку пам’яті, який буде просто очищений при виході, ви іноді будете вловлювати помилки в алокаторі лише тоді, коли перейдете всю структуру даних до звільнити його, і ви будете мати набагато меншу головний біль , якщо ви коли - небудь реорганізувати так , що який - то об'єкт дійсно потрібно отримати звільнятися до виходу з програми.

Основні причини не очищати вручну - це продуктивність, продуктивність, продуктивність та можливість виникнення помилки в два рази або після використання у вашому непотрібному коді очищення, що може перетворитись на помилку аварії або безпеку експлуатувати.

Якщо ви дбаєте про ефективність роботи, ви завжди хочете, щоб профіль дізнався, де ви витрачаєте весь свій час, а не здогадуватися. Можливим компромісом є загортати необов’язковий код очищення в умовний блок, залишати його під час налагодження, щоб ви отримали переваги написання та налагодження коду, а потім, якщо і лише якщо ви емпірично визначили, що це занадто багато Нагорі, скажіть компілятору, щоб пропустити його в остаточному виконаному файлі. І затримка в закритті програми майже не визначається, але майже не виходить на критичному шляху.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.