Як працюють malloc () та free ()?


276

Я хочу знати, як mallocі як freeпрацювати.

int main() {
    unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
    memset(p,0,4);
    strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
    cout << p;
    free(p); // Obvious Crash, but I need how it works and why crash.
    cout << p;
    return 0;
}

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


5
Чи не має насправді це залежати від компілятора та використовуваної бібліотеки часу виконання?
Vilx-

9
це залежатиме від впровадження CRT. Тож узагальнити це не можна.
Naveen

58
що strcpy пише 9 байт, а не 8. Не забувайте термінатор NULL ;-).
Еван Теран


2
@ LưuVĩnhPhúc, це C ++. Зверніть увагу наcout <<
Braden Best

Відповіді:


385

Добре, деякі відповіді про malloc вже були опубліковані.

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

У багатьох реалізаціях malloc / free, free зазвичай не повертає пам'ять операційній системі (або принаймні лише у рідкісних випадках). Причина полягає в тому, що ви отримаєте прогалини у вашій купі, і, таким чином, це може статися, що ви просто закінчите свої 2 або 4 ГБ віртуальної пам’яті з пробілами. Цього слід уникати, адже як тільки віртуальна пам’ять закінчиться, у вас виникнуть справді великі проблеми. Інша причина полягає в тому, що ОС може обробляти лише фрагменти пам'яті, які мають певний розмір та вирівнювання. Для конкретності: Зазвичай ОС може обробляти лише блоки, якими може керувати віртуальний менеджер пам'яті (найчастіше це кратні 512 байт, наприклад, 4 КБ).

Тому повернути 40 байт в ОС просто не вийде. То що ж робить безкоштовно?

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

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

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

Чому ваш код виходить з ладу:

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

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

Це одні з найгірших проблем C / C ++, і одна з причин, чому вказівники можуть бути настільки проблематичними.


63
Soooo багато людей не розуміють, що free () може не повертати пам'ять в ОС, це надихає. Дякуємо, що допомогли просвітити їх.
Артелій

Артелій: навпаки, завжди буде нова воля?
Гійом07

3
@ Guillaume07 Я припускаю, що ви мали на увазі видалення, а не нове. Ні, це не обов'язково. видалити і безкоштовно зробити (майже) те саме. Ось код, який кожен дзвонить у MSVC2013: goo.gl/3O2Kyu
Yay295

1
delete завжди викликає деструктор, але сама пам'ять може потрапити у вільний список для подальшого розподілу. Залежно від реалізації, це може бути навіть той самий безкоштовний список, який використовує malloc.
Девід К.

1
@Juergen Але коли free () читає зайвий байт, який містить інформацію, скільки пам'яті виділено з malloc, він отримує 4. Тоді як стався збій або як вільно () торкнутися адміністративних даних?
Невизначена поведінка

56

Як говорить aluser у цій темі форуму :

У вашому процесі є область пам'яті, від адреси x до адреси y, яка називається купою. Усі ваші дані malloc'd живуть у цій області. malloc () зберігає певну структуру даних, скажімо, список усіх вільних фрагментів місця в купі. Коли ви зателефонуєте на malloc, він перегляне список, який є досить великим для вас, повертає на нього вказівник і записує той факт, що він більше не є безкоштовним, а також наскільки він великий. Коли ви викликаєте free () з тим же вказівником, free () шукає, наскільки великий цей фрагмент, і додає його назад до списку безкоштовних фрагментів (). Якщо ви зателефонуєте malloc (), і він не зможе знайти жодного достатньо великого фрагмента в купі, він використовує системний виклик brk (), щоб наростити купу, тобто збільшити адресу y і викликати всі адреси між старим y і новим y бути дійсною пам'яттю. brk () повинен бути sscall;

malloc () залежить від системи / компілятора, тому важко дати конкретну відповідь. В основному, проте він відслідковує, яку пам'ять вона виділяється, і залежно від того, як це зробити, ваші дзвінки на безкоштовне можуть провалитись або вдатися.

malloc() and free() don't work the same way on every O/S.


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

36

Одна реалізація malloc / free робить наступне:

  1. Отримайте блок пам'яті з ОС через sbrk () (виклик Unix).
  2. Створіть заголовок та колонтитул навколо цього блоку пам'яті з деякою інформацією, такою як розмір, дозволи та місце розташування наступного та попереднього блоку.
  3. Коли надходить дзвінок на malloc, посилається список, який вказує на блоки відповідного розміру.
  4. Потім цей блок повертається і заголовки та колонтитули оновлюються відповідно.

25

Захист пам'яті має деталізацію сторінки і потребує взаємодії з ядром

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

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

calloc (3) і malloc (3) взаємодіють з ядром, щоб отримати необхідну пам'ять. Але більшість реалізацій free (3) не повертають пам'ять до ядра 1 , вони просто додають її у вільний список, з яким calloc () та malloc () звернуться пізніше, щоб повторно використовувати звільнені блоки.

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

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

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

Теорія функціонування

Отже, працюючи назад від вашого прикладу до загальної теорії, malloc (3) отримує пам'ять з ядра, коли воно потребує, і, як правило, в одиницях сторінок. Ці сторінки поділяються або консолідуються, як цього вимагає програма. Malloc та вільна співпраця для підтримки каталогу. Коли вони можливі, вони з'єднують сусідні вільні блоки, щоб мати можливість забезпечити великі блоки. Каталог може або не може залучати використання пам'яті у звільнених блоках для формування пов'язаного списку. (Альтернатива - це трохи більше спільної пам’яті та підключення до підказок, і вона передбачає виділення пам’яті спеціально для каталогу.) Malloc та free мають мало можливостей для забезпечення доступу до окремих блоків, навіть коли спеціальний та необов'язковий код налагодження компілюється в Програма.


1. Той факт, що дуже мало реалізацій спроб вільного () повернення пам’яті в систему, не обов'язково пояснюється тим, що реалізатори відхиляються. Взаємодія з ядром відбувається набагато повільніше, ніж просто виконувати код бібліотеки, і користь мала б. Більшість програм мають стабільний стан або збільшують слід пам’яті, тому час, витрачений на аналіз купи, що потребує повернення пам’яті, був би витрачений повністю. До інших причин можна віднести той факт, що внутрішня фрагментація робить вирівнювання сторінок блоками навряд чи існують, і цілком ймовірно, що повернення блоку буде фрагментувати блоки в будь-яку сторону. Нарешті, кілька програм, які повертають велику кількість пам’яті, ймовірно, обійдуть malloc () та просто розподілять та звільняють сторінки в будь-якому випадку.


Хороша відповідь. Рекомендую документ: Динамічне розподілення зберігання: опитування та критичний огляд Wilson et al. Для глибокого огляду внутрішніх механізмів, таких як поля заголовка та вільні списки, які використовуються алокаторами.
Goaler444

23

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

Наприклад, виділяючи 4 байти, malloc дає вам вказівник на 4 байти. Те, що ви можете не усвідомлювати, - це пам'ять на 8-12 байт раніше вашими 4 байтами використовується malloc для створення ланцюга всієї виділеної вами пам'яті. Коли ви телефонуєте безкоштовно, він забирає ваш покажчик, створює резервну копію, де є його дані, і працює на цьому.

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

ВІДХОДЖЕННЯ: Те, що я описав, - це звичайна реалізація malloc, але аж ніяк не єдина можлива.


12

Ваш рядок strcpy намагається зберегти 9 байт, а не 8, через NUL-термінатор. Це викликає невизначене поведінку.

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

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

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

Все залежить від розподільника пам'яті - у різних реалізаціях використовуються різні механізми.


12

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

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

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


6

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


5

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

Коли ви malloc, блок витягується з вільного списку. Коли ви звільнені, блок буде повернутий у вільний список. Швидше за все, коли ви перезапишете кінець вказівника, ви пишете на заголовку блоку у вільному списку. Коли ви звільняєте пам’ять, free () намагається подивитися на наступний блок і, ймовірно, закінчується натисканням на вказівник, що викликає помилку шини.


4

Ну це залежить від реалізації розподільника пам'яті та ОС.

Наприклад, під Windows, процес може запитати сторінку або більше оперативної пам'яті. Потім ОС призначає ці сторінки до процесу. Однак це не пам'ять, виділена вашій програмі. Розподільник пам'яті CRT буде позначати пам'ять як суцільний блок "доступний". Потім розподільник пам'яті CRT перегляне список безкоштовних блоків і знайде найменший можливий блок, який він може використовувати. Потім він забере стільки, скільки потрібно, і додасть його до "виділеного" списку. Прикріпленим до голови фактичного розподілу пам'яті буде заголовок. Цей заголовок буде містити різні біти інформації (наприклад, він може містити наступний і попередній виділені блоки для формування пов'язаного списку. Він, швидше за все, буде містити розмір виділення).

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

Це не проста проблема. Частина розподільника ОС повністю вийшла з вашого контролю. Я рекомендую вам прочитати щось на кшталт Малока Дуга Леа (DLMalloc), щоб зрозуміти, як буде працювати досить швидкий розподільник.

Редагувати: Ваш збій буде викликаний тим, що написавши більшу кількість, ніж розміщення, ви перезаписали наступний заголовок пам'яті. Таким чином, коли він звільняється, він стає дуже заплутаним у тому, що саме воно вивільняє та як об'єднатись у наступний блок. Це не завжди може призвести до краху прямо на вільному місці. Пізніше це може спричинити збій. Взагалі уникайте перезапису пам'яті!


3

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

Що стосується malloc / free реалізації - цілі книги присвячені цій темі. В основному алокатор отримає більші шматки пам'яті з ОС і керує ними для вас. Деякі проблеми, з якими повинен вирішуватись розподільник,:

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

2

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


1

Також важливо усвідомити, що просто переміщення вказівника перерви програми навколо brkі sbrkфактично не виділяє пам'ять, воно просто встановлює адресний простір. Наприклад, у Linux, коли доступ до цього діапазону адрес буде "підкріплений" фактичною фізичною сторінкою, пам'ять буде "підкріплена", що призведе до помилки сторінки, і в кінцевому підсумку призведе до того, що ядро ​​дзвонить у розподільник сторінки, щоб отримати резервну сторінку.

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