Чи можна статично передбачити, коли розібрати пам'ять --- лише з вихідного коду?


27

Пам'ять (і блокування ресурсів) повертається в ОС у детермінованих точках під час виконання програми. Сам по собі керуючий потік програми достатньо, щоб знати, де, безумовно, може бути розміщений даний ресурс. Так само, як людський програміст знає, куди писати, fclose(file)коли програма робиться з ним.

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

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

Здається, можна написати програму, яка може прочитати джерело програми та:

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

Чи є щось там, що це вже робить? Я не думаю, що Rust або C ++ розумні покажчики / RAII - це те саме.


57
шукати проблему зупинки. Це дідусь, чому питання "Неможливо зробити компілятор, якщо програма робить X?" завжди відповідає "Не в загальному випадку".
храповик виродка

18
Пам'ять (і блокування ресурсів) повертається в ОС у детермінованих точках під час виконання програми.
Ейфорія

9
@ratchetfreak Спасибі, такі речі, як ця проблема зупинки, ніколи не знають, що мені хочеться, щоб я отримав ступінь з комп’ютерних наук замість хімії.
zelcon

15
@ zelcon5, тепер ви знаєте про хімію та проблему зупинки ... :)
Девід Арно

7
@Euphoric, якщо ви не структуруєте свою програму, щоб межі використання ресурсу були дуже чіткими, як у RAII або пробних ресурсів
храповик-виродка

Відповіді:


23

Візьмемо цей (надуманий) приклад:

void* resource1;
void* resource2;

while(true){

    int input = getInputFromUser();

    switch(input){
        case 1: resource1 = malloc(500); break;
        case 2: resource2 = resource1; break;
        case 3: useResource(resource1); useResource(resource2); break;
    }
}

Коли слід безкоштовно називати? перед тим, як malloc і призначити resource1нам, ми не можемо, тому що це може бути скопійовано resource2, перед тим, як призначити, resource2ми не можемо, тому що, можливо, ми отримали 2 від користувача двічі без втручання 1.

Єдиний спосіб бути впевненим - протестувати resource1 та resource2, щоб перевірити, чи не вони рівні у випадках 1 та 2, та звільнити старе значення, якщо вони не були. Це по суті довідковий підрахунок, де ви знаєте, що є лише 2 можливі посилання.


Насправді це не єдиний спосіб; інший спосіб - дозволити існування лише однієї копії. Це, звичайно, має свої проблеми.
Джек Едлі

27

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

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

Можна писати докази щодо поведінки програми без необхідності вирішувати проблему зупинки, але лише якщо ви використовуєте якісь примітки для обмеження програми. Дивіться також докази безпеки (sel4 тощо)


Коментарі не для розширеного обговорення; ця розмова перенесена в чат .
maple_shaft

13

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

Ретроспектива управління пам’яттю, що базується на регіонах, - це стаття оригінальних авторів набору ML, яка вникає в її успіхи та невдачі. Можливо, висновок полягає в тому, що стратегія є практичною під час написання за допомогою групового профілера.

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


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

Зауважте, що проблема стає набагато більш вирішуваною, коли ми говоримо про чисті чи майже чисті функціональні,
cat

10

передбачити всі перестановки керуючого потоку програми

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


влучне зауваження. Я здогадуюсь, що квантові процесори є єдиною надією, якщо вона взагалі є
zelcon

4
@ zelcon5 Ха-ха, ні. Квантове обчислення робить це гірше , а не краще. Це додає додаткові ("приховані") змінні до програми і набагато більше невизначеності. Більшість практичних кодів QC, які я бачив, покладається на "квантовий для швидких обчислень, класичний для підтвердження". Я ледве подряпав поверхню квантових обчислень, але мені здається, що квантові комп'ютери можуть бути не дуже корисними без класичних комп'ютерів, щоб створити резервну копію та перевірити їх результати.
Луаан

8

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

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


Ну, компілятор вважає, що це може звільнити пам'ять; але це може бути не так. Подумайте про поширену помилку початківця щодо повернення вказівника або посилання на локальну змінну. Тривіальні випадки спіймані компілятором, правда; менш тривіальні - не.
Пітер - Відновіть Моніку

Цю помилку припускають програмісти мовами, де програмісти повинні керувати розподілом пам'яті вручну @Peter. Коли компілятор управляє розподілом пам'яті, подібні помилки не трапляються.
Карл Білефельдт

Ну, ви зробили дуже загальне твердження, включаючи фразу "майже всі компілятори", яка повинна включати компілятори C.
Пітер - Відновіть Моніку

2
Компілятори C використовують його для визначення того, які тимчасові змінні можуть бути віднесені до регістрів.
Карл Білефельдт

4

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

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

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

Є деякі альтернативи (деякі виникають).

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

Swift та Objective-C проходять ще одним маршрутом, який здебільшого є автоматичним підрахунком посилань. Підрахунок посилань стикається з проблемами з циклами, і існують значні проблеми програміста, наприклад, особливо із закриттям.


3
Звичайно, GC має витрати, але це також має переваги в роботі. Наприклад, в .NET, виділення з купи майже безкоштовне, оскільки він використовує шаблон "розподілу стека" - просто нарощує покажчик, і все. Я бачив програми, які працюють швидше, переписані навколо .NET GC, ніж вони використовували ручне розподілення пам’яті, насправді не зрозуміло. Аналогічно, підрахунок посилань насправді досить дорогий (просто в різних місцях від GC), і ви не хочете платити, якщо зможете цього уникнути. Якщо ви хочете працювати в режимі реального часу, статичний розподіл часто все ще залишається єдиним способом.
Луаан

2

Якщо програма не залежить від будь-якого невідомого введення, то так, це повинно бути можливим (із застереженням, що це може бути складною задачею і може зайняти багато часу, але це буде справедливо і для програми). Такі програми були б повністю вирішеними під час компіляції; з точки зору C ++, вони могли б (майже) повністю складатися з constexprs. Простими прикладами можуть бути обчислення перших 100 цифр pi або сортування відомого словника.


2

Звільнення пам'яті, як правило, еквівалентно проблемі зупинки - якщо ви не можете статично визначити, чи зупиниться програма (статично), ви не можете сказати, чи звільнить вона і статичну пам'ять.

function foo(int a) {
    void *p = malloc(1);
    ... do something which may, or may not, halt ...
    free(p);
}

https://en.wikipedia.org/wiki/Halting_problem

Однак, Іржа дуже приємна ... https://doc.rust-lang.org/book/ownership.html

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