Почему 'free' в C не принимает количество байтов, которые будут освобождены?


просто чтобы быть ясным: я знаю, что malloc и free реализованы в библиотеке C, которая обычно выделяет куски памяти из ОС и делает свое собственное управление для распределения меньших объемов памяти в приложение и отслеживает количество выделенных байтов. Этот вопрос не в том, как свободный знает, сколько нужно освободить.

скорее, я хочу знать, почему free было сделано таким образом в первую очередь. Будучи языком низкого уровня, я думаю, что это было бы прекрасно разумно попросить программиста C отслеживать не только то, какая память была выделена, но и сколько (на самом деле, я обычно нахожу, что я в конечном итоге отслеживаю количество байтов malloced в любом случае). Мне также приходит в голову, что явно давая количество байтов free может позволить некоторые оптимизации производительности, например, распределитель, который имеет отдельные пулы для разных размеров распределения, сможет определить, какой пул освободить, просто посмотрев на входные аргументы, и там будет будьте меньше места накладных расходов в целом.

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

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

12 80

12 ответов:

один аргумент 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 *). (В настоящее время этот второй вариант выглядит довольно заманчиво, потому что мы знаем, что строки с нулевым завершением являются "самая катастрофическая ошибка дизайна в истории вычислений", но это задним числом говорить. Еще в 70-х годах, способность C обрабатывать строки как простой char * на самом деле считалось определяя преимущество над конкурентами, как Паскаль и Алгол.) Кроме того, это не просто strdup который страдает от этой проблемы -- он влияет на каждую системную или пользовательскую функцию, которая выделяет память кучи.

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

представьте, что вы пишете приложения в C для запуска на V6 Unix, с его двумя аргументами mfree. До сих пор вам удавалось хорошо, но отслеживание этих размеров указателя становится все более и более хлопотным, поскольку ваши программы стать более амбициозной и требуют все больше и больше использования кучи выделенных переменных. Но тогда у вас есть блестящая идея: вместо копирования вокруг этих size_ts все время, вы можете просто написать некоторые служебные функции, которые прячут размер непосредственно внутри выделенной памяти:

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

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

"почему free в C не взять количество байтов, которые будут освобождены?"

здесь в этом нет необходимости, и не совсем имеет смысл в любом случае.

когда вы выделяете что-то, вы хотите сказать системе, сколько байтов выделить (по понятным причинам).

однако, когда вы уже выделили свой объект, теперь определяется размер области памяти, которую вы возвращаете. Это подразумевается. Это один непрерывный блок памяти. вы не можете освободить часть его (давайте забудем realloc(), это не то, что он делает в любом случае), вы можете только освободить все дело. вы не можете "освободить X байт" либо -- вы либо освободить блок памяти, который вы получили от malloc() или нет.

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

например, наиболее типичные реализации malloc() ведение связанного списка указателей на свободные и выделенные блоки памяти. Если вы передадите указатель на free(), он просто будет искать этот указатель в" выделенном "списке, разблокирует соответствующий узел и прикрепит его к" свободному " списку. ему даже не нужен был размер региона. It эта информация понадобится только тогда, когда она потенциально попытается повторно использовать рассматриваемый блок.

C может быть не таким" абстрактным", как C++, но он по-прежнему предназначен для абстракции над сборкой. Для этого из уравнения вынимаются детали самого низкого уровня. Это предотвращает вас от необходимости возиться с выравниванием и дополнением, по большей части, что сделало бы все ваши программы C непереносимыми.

короче, в этом весь смысл написания абстракции.

на самом деле, в Древнем распределителе памяти ядра Unix, на

Почему free в C не взять количество байтов, которые будут освобождены?

потому что это не нужно. информация уже доступна во внутреннем управлении, выполняемом malloc / free.

вот два соображения (которые могли или не могли способствовать этому решению):

  • Почему вы ожидаете, что функция получает параметр не нужен?

    (это усложнит практически все клиентский код, основанный на динамической памяти, и добавляет совершенно ненужную избыточность в ваше приложение). Отслеживание выделения указателя уже является сложной проблемой. Отслеживание распределения памяти вместе с соответствующими размерами приведет к излишнему усложнению клиентского кода.

  • что бы переделывали free функция do, в этих случаях?

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

    это не бесплатное (причина a утечка памяти?)? Игнорировать второй параметр? Остановите приложение, вызвав exit? Реализация этого добавит дополнительные точки отказа в вашем приложении, для функции, которая вам, вероятно, не нужна (и если вам это нужно, см. мой последний пункт ниже - "реализация решения на уровне приложения").

скорее, я хочу знать, почему free был сделан таким образом в первую очередь.

потому что это "правильный" способ сделать это. API должен требовать аргументы, необходимые для выполнения этой операции,и не более.

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

правильные способы реализовать это, являются:

  • (на системном уровне) в рамках реализации malloc - ничто не мешает разработчику библиотеки писать malloc для использования различных стратегий внутри, основанных на полученном размере.

  • (на уровне приложения) путем обертывания malloc и free в ваших собственных API и использования их вместо этого (везде в вашем приложении, которое вам может понадобиться).

на ум приходят пять причин:

  1. это удобно. Оно извлекает всю нагрузку накладных расходов от программиста и избегает типа весьма трудного для того чтобы отслеживать ошибки.

  2. это открывает возможность освобождения части блока. Но поскольку менеджеры памяти обычно хотят иметь информацию об отслеживании, неясно, что это будет означать?

  3. легкость гонок на орбите пятна на о заполнения и выравнивание. Характер управления памятью означает, что фактический

Я только публикую это как ответ не потому, что это тот, на который вы надеетесь, а потому, что я считаю, что это единственный правдоподобно правильный:

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

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


до много (много1!) из вас, кто думает, что передача размера так смешно:

могу ли я сослаться на проектное решение C++для std::allocator<T>::deallocate способ?

void deallocate(pointer p, size_type n);

все nT объекты в области, на которую указывает p должны быть уничтожены до этого вызова.
n должно соответствовать значению, переданному в allocate получить эту память.

Я думаю, что вы будете иметь достаточно "интересные" время анализа этого проектного решения.


как operator delete получается, что N3778 предложение ("размер C++ Освобождение") также предназначено для исправления этого.


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

malloc и free идут рука об руку, причем каждому "malloc" соответствует один "свободный". Таким образом, имеет смысл, что "свободное" соответствие предыдущему "malloc" должно просто освободить объем памяти, выделенный этим malloc - это большинство вариантов использования, которые имели бы смысл в 99% случаев. Представьте себе все ошибки памяти, если все использование malloc / free всеми программистами по всему миру когда-либо понадобится программисту отслеживать сумму, выделенную в malloc, а затем не забудьте бесплатно то же самое. Сценарий, о котором вы говорите, действительно должен использовать несколько mallocs/frees в какой-то реализации управления памятью.

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

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

вы могли бы написать свой собственный распределитель, который работал несколько так, как вы предлагаете хотя и это часто делается в c++ для распределителей пулов в некотором роде аналогичным образом для конкретных случаев (с потенциально огромным увеличением производительности), хотя это обычно реализуется с точки зрения оператора new для выделения блоков пула.

Я не вижу, как будет работать распределитель, который не отслеживает размер своих распределений. Если бы он этого не сделал, как бы он узнал, какая память доступна для удовлетворения будущего malloc запрос? Он должен, по крайней мере, хранить некоторую структуру данных, содержащую адреса и длины, чтобы указать, где находятся доступные блоки памяти. (И, конечно же, хранение списка свободных пространств эквивалентно хранению списка выделенных пространств).

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

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

надеюсь, что это помогает!

Почему это должно быть? malloc() и free () намеренно очень простое управление памятью примитивы, и управление памятью более высокого уровня в C в значительной степени зависит от разработчика. Т

кроме того, realloc() уже делает это - если вы уменьшите выделение в realloc (), он не будет перемещать данные, а возвращаемый указатель будет таким же, как и исходный.

Это в целом верно для всей стандартной библиотеки, что она состоит из простых примитивов из которого вы можете создавать более сложные функции в соответствии с потребностями вашего приложения. Таким образом, ответ на любой вопрос о форме "почему стандартная библиотека не делает X" заключается в том, что она не может делать все, что может подумать программист (для этого нужны программисты), поэтому она предпочитает делать очень мало - создавать свои собственные или использовать сторонние библиотеки. Если вы хотите более обширную стандартную библиотеку, включая более гибкое управление памятью, то c++ может быть ответом.

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