Що відбувається зі сміттям у C ++?


51

У Java є автоматичний GC, який час від часу зупиняє світ, але піклується про сміття на купі. Тепер додатки C / C ++ не мають цих STW заморожувань, а також використання їх пам'яті не зростає нескінченно. Як досягається така поведінка? Як опікуються мертвими предметами?


38
Примітка: зупинка світу - це вибір впровадження деяких сміттєзбірників, але, звичайно, не всіх. Наприклад, є одночасні GC, які працюють одночасно з мутатором (саме так розробники GC називають фактичну програму). Я вірю, що ви можете придбати комерційну версію відкритого джерела IBM JVM J9 з одночасним колектором без пауз. У Azul Zing є колектор "без пауз", який насправді не є паузальним, але надзвичайно швидким, щоб не було помітних пауз (його паузи GC відповідають тому ж порядку, що і контекстний комутатор потоку операційної системи, який зазвичай не сприймається як пауза) .
Йорг W Міттаг

14
У більшості (довгих) програм C ++, якими я користуюся , є використання пам'яті, яке з часом зростає без обмежень. Можливо, ви не звичаєте залишати програми відкритими більше декількох днів одночасно?
Джонатан У ролях

12
Візьміть до уваги, що з сучасним C ++ та його конструкціями вам більше не потрібно видаляти пам'ять вручну (якщо ви не проводите спеціальну оптимізацію), оскільки ви можете керувати динамічною пам'яттю за допомогою розумних покажчиків. Очевидно, це додає певних накладних витрат на розробку C ++, і вам потрібно бути трохи обережнішими, але це не зовсім інша річ, вам просто потрібно пам’ятати, щоб використовувати конструкцію розумного вказівника, а не просто викликати посібник new.
Енді

9
Зауважте, що все-таки можливі витоки пам'яті мовою, зібраною зі сміттям. Я не знайомий з Java, але витоки пам'яті, на жаль, досить поширені в керованому світі GC .NET. Об'єкти, на які опосередковано посилається статичне поле, не збираються автоматично, обробники подій є дуже поширеним джерелом протікань, а недетермінований характер збору сміття не дає змоги повністю усунути необхідність вільно звільняти ресурси (що призводить до ідентифікації візерунок). Все сказане, що правильно використовується модель управління пам'яттю C ++ значно перевершує збирання сміття.
Коді Грей,

26
What happens to garbage in C++? Чи зазвичай це не компілюється у виконуваний файл?
BJ Майєрс

Відповіді:


100

Програміст несе відповідальність за те, щоб об'єкти, створені ними, newбули видалені через delete. Якщо об’єкт створений, але не знищений до того, як останній вказівник чи посилання на нього не виходять за межі, він потрапляє через щілини і стає витоком пам'яті .

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

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

int main()
{
    int* variableThatIsAPointer = new int;
    int variableInt = 0;

    delete variableThatIsAPointer;
}

Тут ми створили дві змінні. Вони існують у Block Scope , як визначено {}фігурними дужками. Коли виконання буде виведено за межі цієї області, ці об'єкти будуть автоматично видалені. У цьому випадку, variableThatIsAPointerяк випливає з назви, - це вказівник на об’єкт в пам'яті. Коли він виходить за межі області, вказівник видаляється, але об’єкт, на який він вказує, залишається. Ось ми deleteцей об’єкт перед тим, як він вийде за межі, щоб переконатися у відсутності витоку пам'яті. Однак ми могли також пропустити цей покажчик в іншому місці і очікували, що він буде видалений пізніше.

Цей характер сфери дії поширюється на класи:

class Foo
{
public:
    int bar; // Will be deleted when Foo is deleted
    int* otherBar; // Still need to call delete
}

Тут застосовується той самий принцип. Нам не потрібно турбуватися про те, barколи Fooбуде видалено. Однак для otherBar, видаляється лише вказівник. Якщо otherBarєдиний дійсний вказівник на будь-який об’єкт, на який він вказує, ми, мабуть, мусимо deleteйого в Fooдеструкторі. Це концепція руху RAII

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

RAII також є типовою рушійною силою Smart Pointers . У стандартній бібліотеці C ++, це std::shared_ptr, std::unique_ptrі std::weak_ptr; хоча я бачив і використовував інші shared_ptr/ weak_ptrреалізації, які відповідають тим же концепціям. Для цього лічильник посилань відстежує кількість покажчиків на даний об’єкт, і автоматично deletes об'єкта, як тільки на нього більше немає посилань.

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


4
видалено через delete- те, що я шукав. Дивовижно.
Джу Шуа

3
Ви можете додати про механізми визначення розміру, передбачені в c ++, які дозволяють велику частину нового та видалення робити переважно автоматичним.
whatsisname

9
@whatsisname - це не те, що нові і видалення робляться автоматично, це те, що вони взагалі не трапляються
Caleth

10
deleteАвтоматично викликається для вас смарт - покажчики , якщо ви використовуєте їх , так що ви повинні розглянути можливість використання їх кожен раз , коли автоматичне зберігання не може бути використаний.
Маріан Спаник

11
@JuShua Зауважте, що при написанні сучасного C ++ вам ніколи не потрібно мати фактичного deleteкоду програми (і від C ++ 14 далі, те саме new), а замість цього використовувати розумні покажчики та RAII, щоб видалити купу об'єктів. std::unique_ptrтип та std::make_uniqueфункція - це пряма, найпростіша заміна newта deleteна рівні коду програми.
Гайд

82

C ++ не має сміття.

Програми C ++ необхідні для утилізації власного сміття.

Щоб зрозуміти це, потрібні програмісти програм C ++.

Коли вони забувають, результат називається "витік пам'яті".


22
Ви, звичайно, переконалися, що ваша відповідь не містить ні сміття, ні
котлоагрегату

15
@leftaroundabout: Дякую Я вважаю це компліментом.
Джон Р. Стром

1
Гаразд у цій відповіді без сміття є ключове слово для пошуку: витік пам'яті. Було б також непогано якось згадати newі delete.
Руслан

4
@Ruslan Те ж саме відноситься і до mallocі free, або new[]і delete[], або будь-які інші розподільники (як для Windows - х GlobalAlloc, LocalAlloc, SHAlloc, CoTaskMemAlloc, VirtualAlloc, HeapAlloc, ...), а також пам'яті , виділеної для вас (наприклад , з допомогою fopen).
користувач253751

43

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

Найбільш базовим засобом є автоматичне зберігання . Багато разів сама мова гарантує утилізацію предметів:

int global = 0; // automatic storage

int foo(int a, int b) {
    static int local = 1; // automatic storage

    int c = a + b; // automatic storage

    return c;
}

У цьому випадку компілятор відповідає за те, коли ці значення не використовуються, і відновлює сховище, пов'язане з ними.

При використанні динамічного зберігання в С традиційно виділяється пам'ять mallocі регенерується за допомогою free. У програмі C ++ пам'ять традиційно розподіляється за допомогою newта відновлюється за допомогою delete.

C протягом багатьох років не змінився, однак сучасний C ++ відмовляється newі deleteповністю, а замість цього покладається на бібліотечні засоби (які самі використовують newі deleteдоцільно):

  • розумні покажчики найвідоміші: std::unique_ptrіstd::shared_ptr
  • але контейнери набагато більш широке поширення на насправді: std::string, std::vector, std::map, ... все внутрішньо управляти динамічно розподіляє пам'яті прозоро

Якщо говорити про shared_ptr, то існує ризик: якщо цикл посилань формується, а не порушується, то там може відбуватися витік пам'яті. Розробник повинен уникати такої ситуації, найпростіший спосіб - уникнути shared_ptrвзагалі, а другий найпростіший - уникнути циклів на рівні типу.

Як результат, витоки пам'яті не є проблемою в C ++ , навіть для нових користувачів, якщо вони утримуються від використання new, deleteабо std::shared_ptr. Це на відміну від С, де необхідна непохитна дисципліна, і взагалі недостатня.


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

Висячий вказівник (або звисаючий посилання) - це небезпека, створена шляхом збереження вказівника або посилання на мертвий об'єкт. Наприклад:

int main() {
    std::vector<int> vec;
    vec.push_back(1);     // vec: [1]

    int& a = vec.back();

    vec.pop_back();       // vec: [], "a" is now dangling

    std::cout << a << "\n";
}

Використання звисаючого вказівника чи посилання - це Невизначена поведінка . Взагалі, на щастя, це негайна крах; На жаль, це, на жаль, спочатку спричиняє пошкодження пам’яті… і час від часу виникає дивна поведінка, оскільки компілятор видає дійсно дивний код.

Не визначена поведінка є найбільшою проблемою із C та C ++ на сьогоднішній день з точки зору безпеки / коректності програм. Ви можете перевірити іржу на мову, де немає збирача сміття та не визначеної поведінки.


17
Re: "Використання звисаючого покажчика або посилання - це не визначена поведінка . Загалом, на щастя, це негайний збій": Дійсно? Це зовсім не відповідає моєму досвіду; навпаки, мій досвід полягає в тому, що використання звисаючого вказівника майже ніколи не викликає негайного збою. . .
ruakh

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

2
"Внаслідок цього витоки пам'яті не є проблемою в C ++". Звичайно, вони є, завжди є C прив'язки до бібліотек для виправлення, а також рекурсивні shared_ptrs або навіть рекурсивні унікальні_ptrs та інші ситуації.
Mooing Duck

3
"Не проблема в C ++, навіть для нових користувачів" - я б визначив це "новими користувачами, які не походять з мови, що нагадує Java або C ".
близько

3
@leftaroundabout: кваліфіковано "до тих пір, поки вони утримуються від використання new, deleteі shared_ptr"; без newі у shared_ptrвас є пряме право власності, тому немає витоків. Звичайно, у вас, швидше за все, є звисаючі покажчики тощо ... але я боюся, що вам потрібно залишити C ++, щоб позбутися цих.
Матьє М.

27

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

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


9
Спільні покажчики (які використовують RAII) забезпечують сучасний спосіб створення витоків. Припустимо, об'єкти A і B посилаються один на одного за допомогою загальних покажчиків, і більше нічого не посилається на об'єкт A або на об'єкт B. Результатом є витік. Це взаємне посилання не є проблемою для мов зі збиранням сміття.
Девід Хаммен

@DavidHammen впевнений, але ціною обходу майже кожного об'єкта, щоб переконатися. Ваш приклад розумних покажчиків ігнорує той факт, що інтелектуальний вказівник сам вийде за межі, і тоді об’єкти будуть звільнені. Ви припускаєте, що розумний вказівник схожий на покажчик, його немає, це об'єкт, який передається навколо стека, як і більшість параметрів. Це не сильно відрізняється від витоків пам'яті, спричинених в мовах GC. наприклад, відомий, коли видалення обробника подій з класу інтерфейсу користувача залишає його беззвучним посиланням і, отже, протікає.
gbjbaanb

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

2
.NET шлях не жбурнути його на підлогу. Це просто тримає його там, де було, поки служниця не прийде. І завдяки тому, що .NET виділяє пам'ять на практиці (а не за контрактом), купа більше схожа на стек з випадковим доступом. Це як би мати купу контрактів і паперів, а також переглядати їх час від часу, щоб відкинути ті, які вже не дійсні. А щоб зробити це простіше, ті, хто пережив кожну відмову, пересуваються в інший стек, так що ви можете уникнути переходу всіх стеків більшу частину часу - якщо перший стек не стане великим, покоївка не торкається інших.
Луань

@Luaan це була аналогія ... Я думаю, ви були б щасливішими, якби я сказав, що залишає банки, лежачи на столі, поки служниця не прийде прибирати.
gbjbaanb

26

Слід зазначити, що у випадку C ++ поширене неправильне уявлення про те, що "потрібно робити керування пам'яттю вручну". Насправді ви зазвичай не керуєте пам'яттю у своєму коді.

Об'єкти фіксованого розміру (зі строком експлуатації)

У переважній більшості випадків, коли вам потрібен об'єкт, об’єкт буде мати певний термін служби у вашій програмі і створюється на стеці. Це працює для всіх вбудованих примітивних типів даних, а також для примірників класів і структур:

class MyObject {
    public: int x;
};

int objTest()
{
    MyObject obj;
    obj.x = 5;
    return obj.x;
}

Об'єкти стека автоматично видаляються, коли функція закінчується. У Java об'єкти завжди створюються на купі, і тому їх потрібно видалити за допомогою якогось механізму, наприклад, збирання сміття. Це не проблема для об'єктів стека.

Об'єкти, що керують динамічними даними (зі строком експлуатації)

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

class MyList {        
public:
    // a fixed-size pointer to the actual memory.
    int* listOfInts; 
    // constructor: get memory
    MyList(size_t numElements) { listOfInts = new int[numElements]; }
    // destructor: free memory
    ~MyList() { delete[] listOfInts; }
};

int listTest()
{
    MyList list(1024);
    list.listOfInts[200] = 5;
    return list.listOfInts[200];
    // When MyList goes off stack here, its destructor is called and frees the memory.
}

У коді, де використовується пам'ять, взагалі немає керування пам'яттю. Єдине, що нам потрібно переконатися - це те, що об’єкт, про який ми писали, має відповідний деструктор. Незалежно від того, як ми покинемо сферу дії listTest, будь то виняток або просто повернувшись з нього, деструктор ~MyList()буде викликаний, і нам не потрібно керувати жодною пам'яттю.

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

В основному всі об'єкти C ++, яким потрібна динамічна пам'ять, використовують цю інкапсуляцію. Він отримав назву RAII ("придбання ресурсів - ініціалізація"), що є досить дивним способом висловити просту думку про те, що об'єкти дбають про власний вміст; те, що вони набувають, - це їх очищення.

Поліморфні об'єкти та життя поза рамками

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

class MyDerivedObject : public MyObject {
    public: int y;
};
std::unique_ptr<MyObject> createObject()
{
    // actually creates an object of a derived class,
    // but the user doesn't need to know this.
    return std::make_unique<MyDerivedObject>();
}

int dynamicObjTest()
{
    std::unique_ptr<MyObject> obj = createObject();
    obj->x = 5;
    return obj->x;
    // At scope end, the unique_ptr automatically removes the object it contains,
    // calling its destructor if it has one.
}

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

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

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

Найкраще: це працює для всіх типів ресурсів

Однією з найбільших переваг RAII є те, що він не обмежується пам'яттю. Це фактично забезпечує дуже природний спосіб управління такими ресурсами, як файли та сокети (відкриття / закриття) та механізми синхронізації, такі як мутекси (блокування / розблокування). По суті, кожним ресурсом, який можна придбати та випустити, керується точно таким же чином у C ++, і жодне з цього управління не залишається користувачеві. Це все інкапсульоване в класи, які набувають у конструкторі та випускають у деструктор.

Наприклад, функція, що блокує мютекс, зазвичай записується так у C ++:

void criticalSection() {
    std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
    doSynchronizedStuff();
} // myMutex is released here automatically

Інші мови роблять це набагато складніше, або вимагаючи, щоб ви це робили вручну (наприклад, у finallyпункті), або вони породили спеціалізовані механізми, які вирішують цю проблему, але не особливо елегантним способом (як правило, пізніше в їхньому житті, коли достатньо людей страждав від недоліку). Такі механізми є пробними ресурсами на Java та використовуючим оператором в C #, обидва з яких є наближеннями RAII C ++.

Отже, підсумовуючи це, все це було дуже поверхневим обліковим записом RAII в C ++, але я сподіваюся, що це допомагає читачам зрозуміти, що управління пам’яттю та навіть управління ресурсами в C ++ зазвичай не є «ручним», а насправді здебільшого автоматичним.


7
Це єдина відповідь, яка не дезінформує людей і не малює С ++ складніше чи небезпечніше, ніж є насправді.
Олександр Рево

6
До речі, поганою практикою вважається використання сировинних покажчиків як власників ресурсів. У їх використанні немає нічого поганого, якщо вони вказують на те, що гарантовано переживе сам покажчик.
Олександр Рево

8
Я другий Олександр. Мені приємно бачити, що "C ++ не має автоматизованого управління пам'яттю. Забудьте, deleteі ви мертві" відповідає на ракетки вище 30 балів і приймаєте, тоді як у цього п'ять. Хтось насправді тут використовує C ++?
Квентін

8

Що стосується конкретно C, мова не дає інструментів для управління динамічно розподіленою пам'яттю. Ви абсолютно відповідальні за те, щоб переконатися, що кожен *allocмає freeдесь відповідне місце .

Там, де справи стають насправді неприємними, це коли розподіл ресурсів закінчується на середині; ти спробуєш ще раз, чи ти відкочуєшся назад і починаєш спочатку, чи повертаєшся назад та виходиш із помилкою, ти просто відшкодуєшся прямо і нехай ОС працює з цим?

Наприклад, ось функція виділення безперервного 2D масиву. Поведінка тут полягає в тому, що якщо помилка розподілу трапляється посередині процесу, ми повертаємо все назад і повертаємо індикацію помилки за допомогою вказівника NULL:

/**
 * Allocate space for an array of arrays; returns NULL
 * on error.
 */
int **newArr( size_t rows, size_t cols )
{
  int **arr = malloc( sizeof *arr * rows );
  size_t i;

  if ( arr ) // malloc returns NULL on failure
  {
    for ( i = 0; i < rows; i++ )
    {
      arr[i] = malloc( sizeof *arr[i] * cols );
      if ( !arr[i] )
      {
        /**
         * Whoopsie; we can't allocate any more memory for some reason.
         * We can't just return NULL at this point since we'll lose access
         * to the previously allocated memory, so we branch to some cleanup
         * code to undo the allocations made so far.  
         */
        goto cleanup;
      }
    }
  }
  goto done;

/**
 * We encountered a failure midway through memory allocation,
 * so we roll back all previous allocations and return NULL.
 */
cleanup:
  while ( i )         // this is why we didn't limit the scope of i to the for loop
    free( arr[--i] ); // delete previously allocated rows
  free( arr );        // delete arr object
  arr = NULL;

done:
  return arr;
}

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

Ви можете полегшити життя, написавши виділені функції розподільника / угоди для кожного ресурсу

Foo *newFoo( void )
{
  Foo *foo = malloc( sizeof *foo );
  if ( foo )
  {
    foo->bar = newBar();
    if ( !foo->bar ) goto cleanupBar;
    foo->bletch = newBletch(); 
    if ( !foo->bletch ) goto cleanupBletch;
    ...
  }
  goto done;

cleanupBletch:
  deleteBar( foo->bar );
  // fall through to clean up the rest

cleanupBar:
  free( foo );
  foo = NULL;

done:
  return foo;
}

void deleteFoo( Foo *f )
{
  deleteBar( f->bar );
  deleteBletch( f->bletch );
  free( f );
}

1
Це гарна відповідь навіть із gotoтвердженнями. Це рекомендується практика в деяких областях. Це загальновживана схема захисту від еквівалента винятків у C. Погляньте на код ядра Linux, який наповнений операційними gotoповідомленнями, і який не протікає.
Девід Хаммен

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

Друга goto- стороння. Було б більш читабельним , якщо ви змінили goto done;в return arr;і arr=NULL;done:return arr;до return NULL;. Хоча в складніших випадках справді може бути декілька gotos, починаючи розгортатися при різних рівнях готовності (що можна зробити за винятком стеку, розмотуючи в C ++).
Руслан

2

Я навчився класифікувати проблеми з пам'яттю на ряд різних категорій.

  • Один раз капає. Припустимо, що програма під час запуску просочується 100 байт, тільки ніколи знову не протікає. Пригортання та усунення цих одноразових витоків - це приємно (мені подобається мати чистий звіт за можливістю виявлення витоку), але це не є істотним. Іноді виникають більші проблеми, на які потрібно напасти.

  • Повторні витоки. Функція, яка викликається повторно протягом життя програми, яка регулярно просочує пам'ять великою проблемою. Ці крапельниці будуть мучити програму та, можливо, ОС, до смерті.

  • Взаємні посилання. Якщо об'єкти A і B посилаються один на одного за допомогою загальних покажчиків, вам потрібно зробити щось особливе, або в дизайні цих класів, або в коді, який реалізує / використовує ці класи, щоб порушити циркулярність. (Це не є проблемою для мов, зібраних зі сміттям.)

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

У мене не було серйозних проблем із витіканнями давно, довго. Використання RAII в C ++ дуже допомагає вирішити ці крапельки та протікання. (Однак потрібно бути обережним із спільними вказівниками.) Набагато важливіше, що у мене виникли проблеми з програмами, використання пам'яті яких постійно зростає та зростає та зростає через незахищені з’єднання з пам'яттю, які вже не використовуються.


-6

Програміст C ++ повинен реалізувати власну форму збору сміття там, де це необхідно. Якщо цього не зробити, це призведе до «витоку пам’яті». Досить поширеним є те, що мови "високого рівня" (наприклад, Java) мають вбудований збір сміття, але мови "низького рівня", такі як C і C ++, не мають.

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