Чому `free` у C не бере кількість байтів, які потрібно звільнити?


84

Щоб зрозуміти: я знаю це mallocі freeреалізовано в бібліотеці C, яка зазвичай виділяє фрагменти пам’яті з ОС та здійснює власне управління для парцеляції менших обсягів пам’яті додатку та відстежує кількість виділених байтів . Це питання не в тому, як безкоштовні знають, скільки безкоштовно .

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

Отже, коротше, чому були mallocі freeстворені такі, що вони повинні внутрішньо відстежувати кількість виділених байтів? Це просто історична аварія?

Невелика редакція: Кілька людей вказали такі пункти, як "а що, якщо ви звільните іншу суму, ніж ту, яку ви виділили". Мій уявлений API може просто вимагати одного, щоб звільнити точно кількість виділених байтів; звільнення більш-менш може бути просто UB або реалізацією. Однак я не хочу перешкоджати дискусіям про інші можливості.


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

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

15
@BoBTFish: Це C ми говоримо не про Python або навіть C ++. Користувач вже повинен зробити $ h! 1 т, він не повинен. Це не причина.
user541686

4
Про це K&R теж нічого сказати. Ми можемо припускати все, що нам подобається, але я думаю, що первісна причина може бути втрачена в історії .
BoBTFish

35
Ви не можете вимагати від програміста правильної передачі розміру блоку, тому що абонент mallocне знає розміру поверненого блоку . mallocчасто повертає блок, більший за запитаний. У кращому випадку програміст міг передати розмір, запитуваний у malloc()виклику, що зовсім не допомогло б реалізатору free().
Бен Войгт,

Відповіді:


97

Один аргумент free(void *)(представлений в Unix V7) має ще одну велику перевагу перед попереднім двоаргументом, про mfree(void *, size_t)який я не бачив тут: один аргумент freeрізко спрощує кожен інший API, який працює з кучевою пам'яттю. Наприклад, якщо freeпотрібен розмір блоку пам'яті, тоді strdupякимось чином доведеться повертати два значення (покажчик + розмір) замість одного (покажчик), а C робить багатозначні повернення набагато громіздкішими, ніж однозначні. Замість того char *strdup(char *), щоб писати, char *strdup(char *, size_t *)або ще struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *). (На сьогоднішній день цей другий варіант виглядає досить спокусливо, тому що ми знаємо, що закінчувані NUL рядки - це "найбільш катастрофічна помилка дизайну в історії обчислень", але це зворотний погляд. Ще в 70-х роках здатність С обробляти рядки як просту char *насправді вважалася визначальною перевагою перед конкурентами, такими як Паскаль та Алгол .) Крім того, strdupця проблема страждає не тільки - вона впливає на кожну визначену системою чи користувачем функція, яка виділяє купу пам'яті.

Ранні дизайнери Unix були дуже кмітливими людьми, і є багато причин, чому freeце краще, ніж mfreeце, в основному, я думаю, що відповідь на питання полягає в тому, що вони це помітили і відповідно розробили свою систему. Сумніваюся, ви знайдете якийсь прямий запис того, що відбувалося в їхніх головах на момент прийняття ними такого рішення. Але ми можемо уявити.

Робіть вигляд, що пишете програми на C для запуску на V6 Unix з двома аргументами mfree. Наразі у вас все вийшло, але відстеження цих розмірів покажчиків стає дедалі більше клопотом, оскільки ваші програми стають більш амбіційними та вимагають дедалі більше використання змінних, призначених для купи. Але тоді у вас є блискуча ідея: замість того, щоб постійно копіювати навколо них size_t, ви можете просто написати деякі утилітні функції, які зберігають розмір безпосередньо у виділеній пам'яті:

void *my_alloc(size_t size) {
    void *block = malloc(sizeof(size) + size);
    *(size_t *)block = size;
    return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
    block = (size_t *)block - 1;
    mfree(block, *(size_t *)block);
}

І чим більше коду ви пишете, використовуючи ці нові функції, тим вони неймовірнішими. Вони не тільки полегшують написання вашого коду, але й роблять його швидшим - дві речі, які часто не поєднуються! До того, як ви передавали ці size_ts повсюди, що додало накладні витрати на процесор для копіювання, і це означало, що вам доводилося частіше розливати регістри (зокрема, додаткові аргументи функції), а також марну пам'ять (оскільки вкладені виклики функцій часто призводять до у декількох копіях того, size_tщо зберігається в різних кадрах стека). У вашій новій системі вам все одно доведеться витратити пам'ять для зберіганняsize_t, але лише один раз, і він ніколи ніде не копіюється. Це може здатися невеликою ефективністю, але майте на увазі, що мова йде про висококласні машини з 256 КБ оперативної пам'яті.

Це вас радує! Тож ви ділитеся своїм крутим трюком з бородатими чоловіками, які працюють над наступним випуском Unix, але це не радує їх, а засмучує. Розумієте, вони якраз збиралися додати купу нових функцій, таких як утиліта strdup, і вони розуміють, що люди, які використовують ваш крутий трюк, не зможуть використовувати свої нові функції, оскільки всі їхні нові функції використовують громіздкий покажчик + розмір API. І тоді це також засмучує вас, бо ви розумієте, що вам доведеться переписувати хорошу strdup(char *)функцію самостійно у кожній написаній вами програмі, замість того, щоб мати можливість використовувати версію системи.

Але почекай! Це 1977 рік, і зворотна сумісність не буде винайдена ще протягом 5 років! І крім того, ніхто серйозно насправді не використовує цю незрозумілу річ "Unix" з її нефарбовою назвою. Перше видання K&R вже зараз на шляху до видавця, але це не проблема - прямо на першій сторінці сказано, що "C не надає жодних операцій для безпосередньої роботи з складеними об'єктами, такими як рядки символів ... немає купи ... ". На даний момент в історії є string.hі mallocрозширення постачальників (!). Отже, припускає Бородатий Чоловік №1, ми можемо змінювати їх як завгодно; чому ми просто не оголосимо ваш хитрий розподільник офіційним розподільником?

Кілька днів потому Бородатий Чоловік №2 бачить новий API і каже, пристосуйтесь, це краще, ніж раніше, але він все одно витрачає ціле слово на розподіл, зберігаючи розмір. Він розглядає це як наступну річ для блюзнірства. Всі інші дивляться на нього, як на божевільного, бо що ти ще можеш зробити? Тієї ночі він затримується до кінця і вигадує новий розподільник, який взагалі не зберігає розмір, а натомість виводить його на льоту, виконуючи бітову зміну чорної магії на значення покажчика, і міняє місцями, зберігаючи новий API на місці. Новий API означає, що ніхто не помічає перемикач, але вони помічають, що наступного ранку компілятор використовує на 10% менше оперативної пам'яті.

І тепер усі раді: Ви отримуєте свій простіший для написання та швидший код, Бородатий Чоловік №1 пише приємний простий, strdupяким люди насправді користуватимуться, а Бородатий Чоловік №2 - впевнений, що трохи заробив - - повертається до базікання з quines . Відправте!

Або, принаймні, так могло статися.


14
Помиляйте, на випадок, якщо незрозуміло, це політ фантазії, з підкріпленими деталями, вкинутими, щоб надати художню універсальність. Будь-яка схожість із живими або мертвими - це суто тому, що всі залучені люди виглядали однаково . Будь ласка, не плутайте з фактичною історією.
Натаніель Дж. Сміт

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

Ого, відповідь на цій сторінці, яка насправді звучить правдоподібно. +1 від мене.
user541686

Ще краще - відповідь на цій сторінці, яка насправді приємна! +1 також.
Девід К. Ранкін,

Цікаво, чи використовувала якась помітна система Pascal сміттєвий пул рядків, подібний до інтерпретаторів BASIC мікрокомп’ютерів? Семантика C не працювала б із подібним, але в Pascal з таким можна було б впоратися досить добре, якби код підтримував відстежувані фрейми стеків (що багато хто з компіляторів і так робив).
supercat

31

"Чому freeв C не береться кількість байт для звільнення?"

Тому що в цьому немає потреби, і це все одно не мало б сенсу .

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

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

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

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


Якщо я позичу у вас 100 доларів, а потім знову позичаю у вас 100 доларів, а потім роблю це ще п’ять разів, тобі насправді все одно, що я позичав у тебе сім разів (якщо ти насправді не нараховуєш відсотки!) Або вам просто байдуже, що я позичив у вас 700 доларів? Те саме: система піклується лише про нерозподілену пам’ять, її не цікавить (і не потрібно і не повинно) те, як розподілена виділена пам’ять.
user541686

1
@Mehrdad: Ні, і ні. C, однак, робить. Вся його мета - зробити речі (трохи) безпечнішими. Я справді не знаю, що ви тут шукаєте.
Гонки легкості на орбіті

12
@ user3477950: Не потрібно передавати кількість байтів : Так, бо він був розроблений таким чином. ОП запитав, чому він був розроблений саме таким чином?
Дедулікатор

5
"Тому що це не потрібно" - це могло бути так само добре розроблено так, що це потрібно.
user253751

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

14

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

Коротше, у цьому вся суть написання реферату .


9
Не знаю, яке відношення до цього має вирівнювання чи відступ. Відповідь насправді нічого не відповідає.
user541686

1
@Mehrdad C не є мовою x86, він намагається (більш-менш) бути портативним і тим самим звільняє програміста від цього значного навантаження. Ви в будь-якому випадку можете досягти цього рівня різними іншими способами (наприклад, вбудована збірка), але абстракція - це головне. Я згоден з цією відповіддю.
Марко А.

4
@Mehrdad: Якщо ви попросили mallocN байт, і він замість цього повернув вказівник на початок цілої сторінки (через вирівнювання, відступ або інші обмеження , користувач не міг би відстежувати це - змусивши їх робити це було б контрпродуктивно.
Майкл Фукаракіс,

3
@MichaelFoukarakis: mallocпросто завжди може повернути вирівняний покажчик, ніколи не зберігаючи розмір виділення. freeпотім може округлити до відповідного вирівнювання, щоб переконатися, що все звільнено належним чином. Я не бачу, де проблема.
user541686

6
@Mehrdad: Немає користі від усієї тієї додаткової роботи, про яку ви щойно згадали. Крім того, передача sizeпараметра freeвідкриває інше джерело помилок.
Майкл Фукаракіс,

14

Власне, у стародавньому розподільнику пам'яті ядра Unix mfree()взяли sizeаргумент. malloc()і mfree()зберігав два масиви (один для основної пам'яті, інший для обміну), що містив інформацію про вільні адреси та розміри блоків.

До Unix V6 не було розподілювача простору користувачів (програми просто використовували sbrk()). У Unix V6 iolib включав розподільник alloc(size)та free()виклик, який не приймав аргументу розміру. Кожному блоку пам'яті передували його розмір і вказівник на наступний блок. Вказівник використовувався лише на вільних блоках, під час проходження вільного списку, і використовувався повторно як пам'ять блоків на використовуваних блоках.

У Unix 32V та Unix V7 це було замінено новим malloc()та free()реалізацією, де аргумент free()не sizeбрався. Реалізація являла собою круговий список, кожному фрагменту передувало слово, яке містило вказівник на наступний фрагмент та біт "зайнято" (виділено). Отже, malloc()/free()навіть не відстежував явного розміру.


10

Чому freeв C не береться кількість байт для звільнення?

Бо не потрібно. Інформація вже доступна у внутрішньому управлінні, що виконується malloc / free.

Ось два міркування (які можуть сприяти цьому рішенню або не сприяти йому):

  • Чому ви очікуєте, що функція отримає не потрібний їй параметр?

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

  • Що зробить змінена freeфункція в цих випадках?

    void * p = malloc(20);
    free(p, 25); // (1) wrong size provided by client code
    free(NULL, 10); // (2) generic argument mismatch
    

    Чи не було б це безкоштовно (спричинити витік пам'яті?)? Ігнорувати другий параметр? Зупинити програму, зателефонувавши виходу? Реалізація цього додала б додаткових точок відмов у вашому додатку для функції, яка вам, мабуть, не потрібна (а якщо вона вам потрібна, див. Мій останній пункт нижче - "впровадження рішення на рівні програми").

Швидше, я хочу знати, чому спочатку так зробили безкоштовний.

Тому що це «правильний» спосіб це зробити. API повинен вимагати аргументів, необхідних для його роботи, і не більше того .

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

Правильними способами реалізації цього є:

  • (на системному рівні) в рамках реалізації malloc - ніщо не заважає реалізатору бібліотеки писати malloc для внутрішнього використання різних стратегій на основі отриманого розміру.

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


6
@ user3477950: Не потрібно передавати кількість байтів : Так, бо він був розроблений таким чином. ОП запитав, чому він був розроблений саме таким чином?
Дедулікатор

4
"Тому що це не потрібно" - це могло бути так само добре розроблено так, що це потрібно, не зберігаючи цю інформацію.
user253751

1
Що стосується Вашого пункту 2, Ви мене замислюєтесь, чи вільна (NULL) є визначеною поведінкою. Aha, «Все стандарти , сумісні версії бібліотеки C частування безкоштовно (NULL) , що не-оп» - джерело stackoverflow.com/questions/1938735 / ...
Mawg говорить відновить Моніку

10

Пам’ятають п’ять причин:

  1. Це зручно. Це знімає з програміста ціле навантаження на накладні витрати і уникає класу надзвичайно важких для відстеження помилок.

  2. Це відкриває можливість звільнення частини блоку. Але оскільки менеджери пам’яті зазвичай хочуть мати інформацію про відстеження, незрозуміло, що це означало б?

  3. Lightness Races In Orbit - це місце, яке стосується прокладки та вирівнювання. Характер управління пам'яттю означає, що фактично виділений розмір цілком можливо відрізняється від розміру, про який ви просили. Це означає, що потрібно було б freeзмінити розмір, а також місце розташування, mallocщоб повернути дійсний розмір, також виділений.

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

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

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

Додано пізніше

Інша відповідь, досить смішна, наводить std :: allocator як доказ того, що це freeможе працювати таким чином, але насправді він служить хорошим прикладом того, чому freeце не працює. Є дві ключові різниці між тим, що malloc/ freeробити, і тим, що робить std :: allocator. Під - перше, mallocі freeякі користувач стикається - вони призначені для загальних програмістів працювати - а std::allocatorпризначений для фахівця виділення пам'яті в стандартній бібліотеці. Це дає гарний приклад того, коли перший з моїх пунктів не має значення чи не матиме значення. Оскільки це бібліотека, труднощі з управлінням складністю розміру відстеження в будь-якому випадку приховані від користувача.

По-друге, std :: allocator завжди працює з елементом одного розміру, це означає, що він може використовувати спочатку передану кількість елементів, щоб визначити, скільки вільного. Чому це відрізняється від freeсамого себе, наочно. У std::allocatorелементах, що підлягають виділенню, завжди однакові, відомі, розміри та завжди однакові предмети, тому вони завжди мають однакові вимоги до вирівнювання. Це означає, що розподільник може бути спеціалізованим, щоб просто розподілити масив із цих елементів на початку та розподілити їх за потреби. Ви не могли цього зробити, freeтому що немає жодного способу гарантувати, що найкращим розміром для повернення є розмір, який запитується, натомість набагато ефективніше повертати більші блоки, ніж той, хто вимагає, * і, отже, абокористувачеві або менеджеру потрібно відстежувати точний розмір, який фактично надано. Передача користувачеві подібних деталей реалізації є непотрібним головним болем, який не приносить користі абоненту.

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


No1 - це правда. No2: не впевнений, що ти маєш на увазі. Не настільки впевнений щодо №3, вирівнювання не вимагає зберігання додаткової інформації. №4 - це кругові міркування; Менеджери пам'яті вимагають лише накладних витрат на шматок, оскільки вони зберігають розмір, тому ви не можете використовувати це як аргумент, чому вони зберігають розмір. І №5 дуже спірний.
user541686

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

2
@jaymmer: Так, це передчасно. Я пропоную почекати більше доби-двох, перш ніж прийняти, і пропоную подумати над цим самостійно. Це справді цікаве питання, і більшість / усіх відповідей, які ви отримаєте спочатку на будь-яке подібне запитання "чому" на StackOverflow, будуть просто напівавтоматичними спробами виправдати поточну систему, а не насправді вирішити основне питання.
user541686

@Mehrdad: Ви неправильно зрозуміли, про що я кажу в №4. Я не кажу, що це займе додатковий розмір, я кажу (а) він переміститься, хто повинен зберегти розмір, щоб він фактично не заощадив простір, і (б) отримана зміна насправді може зробити менш ефективний не більше. Щодо №5, я не впевнений, що це взагалі дискусійно: ми - максимум - говоримо про те, щоб зберегти пару інструкцій із безкоштовного дзвінка. У порівнянні з витратами на безкоштовний дзвінок, який буде незначним.
Jack Aidley

1
@Mehrdad: О, і на # 3, ні, це не вимагає додаткової інформації, воно вимагає додаткової пам'яті. Типовий диспетчер пам'яті, призначений для роботи з вирівнюванням по 16 байт, поверне покажчик на 128-байтовий блок при запиті на 115-байтний блок. Якщо freeвиклик повинен правильно передати розмір, який потрібно звільнити, він повинен це знати.
Джек Едлі,

5

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

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

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


До Багаточасткові (багато 1 !), Хто думає , передаючи розмір так смішно:

Чи можу я звернутись до дизайнерського рішення std::allocator<T>::deallocateметоду C ++ ?

void deallocate(pointer p, size_type n);

Усі об'єкти в районі, на який вказує, повинні бути знищені до цього виклику. має відповідати значенню, переданому для отримання цієї пам’яті.n Tp
nallocate

Я думаю, у вас буде досить "цікавий" час, аналізуючи це дизайнерське рішення.


Що стосується operator delete, виявляється, що пропозиція N3778 2013 року ("Розміщення розміру C ++") також має на меті виправити це.


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


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

1
@cmaster: Ці випадки використання були саме тими, про які я мав на увазі, ти вдарив цвях по голові, дякую. Щодо фрагментації: не впевнений, як це збільшує фрагментацію щодо альтернативи, яка полягає у виділенні та вивільненні пам'яті невеликими шматками.
user541686

std::allocatorвиділяє лише елементи певного, відомого розміру. Це не розподільник загального призначення, порівняння - яблука та апельсини.
Джек Ейдлі,

Мені здається, що частково філософським, частково дизайнерським рішенням було прийнято зробити стандартну бібліотеку на С мінімальним набором примітивів, з яких можна будувати практично все - спочатку задумано як мову рівня системи та переносимо до систем май, що цей примітивний підхід робить сенс. З C ++ було прийнято інше рішення зробити стандартну бібліотеку дуже обширною (і збільшити за допомогою C ++ 11). Швидша розробка апаратного забезпечення, більші обсяги пам’яті, більш складні архітектури та необхідність звернутися до розробки додатків вищого рівня, можливо, сприяють цій зміні акцентів.
Кліффорд

1
@Clifford: Саме так - саме тому я сказав, що для цього немає переконливої ​​причини. Це просто рішення, яке було прийнято, і немає підстав вважати, що воно суворо краще, ніж альтернативи.
user541686

2

malloc та безкоштовні йдуть рука об руку, при цьому кожен «malloc» відповідає одному «free». Таким чином, цілком логічно, що "вільний", що відповідає попередньому "malloc", повинен просто звільнити обсяг пам'яті, виділений цим malloc - це більшість випадків використання, які мали б сенс у 99% випадків. Уявіть собі всі помилки пам’яті, якщо всі програми malloc / free усіма програмістами у всьому світі коли-небудь потребувалимуть програміста відстежувати кількість, виділену в malloc, а потім пам’ятати про те, щоб звільнити те саме. Сценарій, про який ви говорите, насправді повинен використовувати кілька mallocs / frees в якійсь реалізації управління пам'яттю.


5
Я думаю, що "Уявіть, що всі [...] помилки" спірні, коли ви думаєте про інші фабричні помилки, такі як gets, printfручні цикли (окремо), невизначені способи поведінки, рядки форматування, неявні перетворення, бітові фокуси тощо цетера.
Себастьян Мах

1

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

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

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


1

Я не бачу, як би працював розподільник, який не відстежує розмір його виділень. Якби він цього не зробив, як би він знав, яка пам’ять доступна для задоволення майбутнього mallocзапиту? Він повинен принаймні зберігати якусь структуру даних, що містить адреси та довжини, щоб вказати, де знаходяться доступні блоки пам'яті. (І звичайно, зберігання списку вільних просторів еквівалентно збереженню списку виділених просторів).


Для цього навіть не потрібне явне поле розміру. Він може просто мати вказівник на наступний блок і виділений біт.
ninjalj

0

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

Якщо ви працюєте в Linux і хочете знати кількість байтів / позицій, які виділив malloc, ви можете створити просту програму, яка використовує malloc один раз або n разів і роздруковує отримані вказівники. Крім того, ви повинні зробити програму сплячою на кілька секунд (достатньо, щоб зробити наступне). Після цього запустіть цю програму, знайдіть її PID, напишіть cd / proc / process_PID і просто введіть "cat maps". Вихідні дані в одному конкретному рядку покажуть як початкову, так і кінцеву адреси пам'яті області пам'яті купи (тієї, в якій ви виділяєте пам'ять динамічно). Якщо ви роздрукуєте покажчики на ці виділені області пам'яті, ви Ви можете здогадатися, скільки пам'яті Ви виділили.

Сподіваюся, це допоможе!


0

Чому це потрібно? malloc () та free () - навмисно дуже прості примітиви управління пам’яттю, а управління пам’яттю вищого рівня в C значною мірою залежить від розробника. Т

Більше того, realloc () це вже робить - якщо ви зменшите розподіл у realloc (), це не буде переміщувати дані, а повернутий покажчик буде однаковим з оригіналом.

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

Ви помітили запитання як на C ++, так і на C, і якщо ви використовуєте C ++, то вам навряд чи слід використовувати malloc / free у будь-якому випадку - крім new / delete, класи контейнерів STL управляють пам'яттю автоматично і, мабуть спеціально відповідати характеру різних контейнерів.


Більше того, realloc () це вже робить - якщо ви зменшите розподіл у realloc (), це не буде переміщувати дані, а повернутий покажчик буде однаковим з оригіналом. Це гарантована поведінка? Здається, це поширене явище у кількох вбудованих розподільниках, але я не був впевнений, чи така поведінка була вказана як частина стандарту-c.
rsaxvc

@rsaxvc: Гарне запитання - документи cplusplus.com "Якщо новий розмір більший, значення новорозподіленої частини невизначене." , маючи на увазі, що якщо він менший, він визначальний . [opengroup.org () говорить: "Якщо новий розмір об'єкта пам'яті вимагає переміщення об'єкта, звільняється простір для попередньої інстанції об'єкта". - якби вона була меншою, дані не потрібно було б переміщувати. Знову ж таки, випливає, що менші не перерозподілятимуться. Я не впевнений, що говорить стандарт ISO.
Кліффорд

1
reallocце абсолютно дозволено переміщувати дані. Відповідно до стандарту C, це абсолютно законно застосовувати reallocяк malloc+ memcpy+ free. І є вагомі причини, чому реалізація може захотіти перемістити розподіл, який був зменшений, наприклад, щоб уникнути фрагментації пам'яті.
Nathaniel J. Smith
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.