Як безкоштовний знає, скільки безкоштовно?


384

У програмуванні на C ви можете безкоштовно передавати будь-який вказівник, який вам подобається як аргумент, як він може знати розмір виділеної пам'яті, щоб звільнити? Щоразу, коли я передаю вказівник на якусь функцію, я також повинен передавати розмір (тобто масив з 10 елементів повинен отримувати 10 як параметр, щоб знати розмір масиву), але мені не потрібно передавати розмір на вільна функція. Чому б ні, і чи можу я використовувати цей самий прийом у своїх власних функціях, щоб врятувати мене від необхідності робити кошики навколо додаткової змінної довжини масиву?


Аналогічне запитання: stackoverflow.com/questions/851958/… (хоча я б сказав, що це не зовсім повторюється)
Джон Картер

Система приятелів - це ще один спосіб зробити це, який можна зрозуміти на основі вказівника, без накладних витрат у кожному блоці.
EvilTeach

Ця публікація це добре пояснює: stackoverflow.com/questions/1957099/…
Зеешан Махмуд

Відповіді:


348

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

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


44
FYI, наприклад, BSD malloc_size()повинен надійно отримувати доступ до розміру блоку з malloc()ед-покажчика. Але немає надійного, портативного способу.
laalto

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

39
@gs Ну, це залежить від реалізації. Але, так, саме там зазвичай є.
Фалаїна

31
Чи можете ви уявити жах, якщо free()програміст вимагав точно повідомити, наскільки великий malloc()блок був? Пам'ять протікає досить погано, як є.
MusiGenesis

35
Чому ця інформація доступна malloc()і free(), але вам потрібно зберігати розмір масиву? Чому б вони не зробили можливим щось подібне, blockSize(ptr)якщо вони все одно зберігають інформацію?
corsiKa

144

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

Один типовий спосіб (рядковий) - це фактично розподілити як заголовок, так і пам’ять, про яку ви просили, доповнивши деякий мінімальний розмір. Наприклад, якщо ви попросили 20 байт, система може виділити 48-байтний блок:

  • 16-байтний заголовок, що містить розмір, спеціальний маркер, контрольну суму, покажчики на наступний / попередній блок тощо.
  • 32 байт області даних (ваші 20 байтів підкреслені кратним 16).

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

 ____ The allocated block ____
/                             \
+--------+--------------------+
| Header | Your data area ... |
+--------+--------------------+
          ^
          |
          +-- The address you are given

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

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

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


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

Інші, які я розробив, мали різні пули для 16-байтних фрагментів, 64-байтових, 256-байтних та 1К-фрагментів, знову використовуючи бітову маску, щоб визначити, які блоки використовуються чи доступні.

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


@paxdiablo Це означає, що malloc не виділяє суміжні блоки пам'яті?
user10678

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

Пов'язане запитання: Чому не існує варіації malloc / free, де ви вказуєте розмір при звільненні, і тому він не повинен зберігати розмір?
користувач253751

@ User253751, тому що тоді Theer одна більш , що вам потрібно стежити, над і вище самого покажчика. Це непотрібне і небезпечне: void *x = malloc(200); free(x, 500);це НЕ скінчиться добре :-) В будь-якому разі, для ефективності, фактичного розміром буфера може бути більше (ви просто не можете покладатися на це).
paxdiablo

@paxdiablo Це також дозволяє уникнути втрати пам'яті на розмір.
користувач253751

47

Зі списку comp.lang.cпоширених запитань: Як безкоштовно знає, скільки байтів безкоштовно?

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


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

Це справді деталі реалізації для malloc api, і немає api, щоб повернути цю інформацію стандартним способом (наскільки мені відомо). "Система" записує його і використовує це free. Можливо, відповідь вас не
влаштовує,

6

Ця відповідь перенесена з пункту « Як безкоштовно () знає, скільки пам’яті розібрати? де мені було не дозволено відповісти явно повторюваним запитанням. Тоді ця відповідь має стосуватися цього дубліката:


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

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

Спроба до freeбудь-якого вказівника зі значенням, відмінним від значення, поверненого попередніми mallocs, і ще невідомі - це помилка. Неможливо частково звільнити регіони пам'яті, повернені з malloc.


Я змінив значення вказівника, повернутого викликом malloc. І звільнив це без помилки. Чому? Дивіться тут: stackoverflow.com/questions/42618390 / ...
smwikipedia

4

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


3

malloc()і free()залежать від системи / компілятора, тому важко дати конкретну відповідь.

Більше інформації щодо цього іншого питання .


2
Вони дійсно залежать від бібліотеки (зазвичай це бібліотека С, яка зазвичай дуже тісно пов'язана з ОС). Для компілятора вони просто функції.
Стипендіати доналу

2

Менеджер купи зберігає об'єм пам'яті, що належить виділеному блоку десь, коли ви дзвонили malloc.

Я ніколи себе не реалізовував, але, мабуть, пам'ять прямо перед виділеним блоком може містити метаінформацію.


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

2

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

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

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

Загалом, відповідь така: окрема структура даних виділена для збереження стану.


1

Щоб відповісти на другу половину вашого запитання: так, ви можете, і досить поширеною схемою в C є наступне:

typedef struct {
    size_t numElements
    int elements[1]; /* but enough space malloced for numElements at runtime */
} IntArray_t;

#define SIZE 10
IntArray_t* myArray = malloc(sizeof(intArray_t) + SIZE * sizeof(int));
myArray->numElements = SIZE;

Це зовсім інша техніка, яку використовує BSD malloc для невеликих об'єктів (хоча це абсолютно гарна техніка для створення масивів у стилі Паскаль)
Pete Kirkham

0

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


0

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

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