Почему не нужно указывать размер освобождаемого блока для free()

Почему не нужно указывать размер освобождаемого блока для free()

Grokking C++

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

Вопрос скорее в другом. Почему вообще malloc+free было создано таким образом. Поскольку C - это язык низкого уровня,  было бы вполне разумно попросить программиста C отслеживать не только то, какая память была выделена, но и ее объем (ведь мы в 99.99999999% случаев сохраняем размер выделенного блока и так или иначе несем его через весь код)). Кажется, что явное указание количества байтов для освобождения может позволить некоторую оптимизацию производительности. Например, аллокатор, имеющий отдельные пулы для разных размеров блоков, сможет определить, каким пулом нужно оперировать, просто взглянув на входные аргументы, и в целом будет меньше накладных расходов на пространство.

Короче говоря, почему malloc и free были созданы так, что от них требуется внутреннее отслеживание количества выделенных байтов? Это просто историческая случайность или все сложнее?

Функция 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 *);


Сегодня второй вариант выглядит довольно заманчиво, потому что мы знаем, что строки, завершающиеся NULL, являются самой катастрофической ошибкой проектирования в истории вычислений, но это, если говорить задним числом. Еще в 70-х годах способность C обрабатывать строки как простые char * на самом деле считалось определяющим преимуществом перед конкурентами, такими как Pascal и Algol.) Кроме того, от этой проблемы страдает не только strdup — он затрагивает каждую системную или пользовательскую функцию, которая распределяет динамическую память.

Первые разработчики Unix были очень умными людьми, и есть много причин, почему free лучше, чем mfree, поэтому я думаю, что ответ на вопрос заключается в том, что они заметили это и разработали свою систему соответствующим образом. Я сомневаюсь, что вы найдете какие-либо прямые записи о том, что происходило в их головах в тот момент, когда они приняли это решение. Но мы можем поиграть в экстрасенсов и увидеть прошлое.

Представьте, что вы пишете приложения на языке C для работы в Unix V6 с ее двумя аргументами 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_t повсюду, что добавляло нагрузку на ЦП при копировании и означало, что вам приходилось чаще разгружать регистры (особенно для дополнительных аргументов функции) и тратить впустую память (поскольку вызовы вложенных функций часто приводят к нескольким копиям size_t, хранящихся в разных кадрах стека). В вашей новой системе вам все равно придется тратить память на хранение size_t, но только один раз, и он никогда никуда не копируется. Это может показаться небольшой эффективностью, но имейте в виду, что мы говорим о допотопных динозаврах с 256 КБ ОЗУ.

Это небольшое улучшение делает вас совершенно окрыленным! Итак, вы делитесь своим крутым трюком с бородачами, которые работают над следующим выпуском Unix, но это их не радует, а огорчает. Видите ли, они как раз добавляли кучу новых служебных функций, таких как strdup, и поняли, что люди, использующие ваш крутой трюк, не смогут использовать их новые функции, потому что все их новые функции используют громоздкий указатель + размер API. И это вас тоже огорчает, потому что вы понимаете, что вам придется переписывать хорошую функцию strdup(char *) самостоятельно в каждой написанной вами программе, вместо того, чтобы иметь возможность использовать системную версию.

Но подождите! На дворе 1977 год, и обратная совместимость не будет изобретена еще лет 5. И кроме того, никто серьезно на самом деле не использует эту малоизвестную штуку «Unix» с ее необычным названием. И прямо на первой странице руководства по С написано, что «C не предоставляет операций для непосредственной работы с составными объектами, такими как символьные строки … здесь нет кучи». На данном этапе истории string.h и malloc являются вендорными расширениями (!). Итак, предлагает важный бородач, мы можем изменить их, как захотим; почему бы нам просто не объявить ваш хитрый распределитель официальным распределителем?

Собственно, так и сделали. И все стали счастливы)

Возможно было все не так. Но суть вы уловили)

Простота API и ограждения пользователя от деталей реализации - одни из главных принципов в проектировании в принципе. И в этом случае мы видим четкую приверженность этим принципам.

Надеюсь, вам было интересно провести со мной это увлекательное путешествие на 40 лет назад.

Stay entertained. Stay cool.


Report Page