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


84

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

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

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

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


18
Потому что отслеживать сами выделения уже проблематично, и это даже усложнило бы код, если бы вам дополнительно пришлось отслеживать размер.
Йенс Густедт

11
Я могу придумать несколько причин: Зачем заставлять пользователя делать это, если им не нужно? Что, если пользователь все испортит? В любом случае это излишний вопрос. Если бы они сделали другой выбор, вы бы все равно спросили, почему.
BoBTFish

15
@BoBTFish: Мы говорим о C, а не о Python или даже C ++. Пользователь уже должен сделать $ h! 1 тонну, в которой он не должен. Это не повод.
user541686

4
K&R тоже нечего сказать по этому поводу. Мы можем спекулировать сколько угодно, но я думаю, что первоначальная причина может быть потеряна в истории .
BoBTFish

36
Вы не можете потребовать от программиста правильно передать размер блока, потому что вызывающий mallocне знает размер возвращенного блока . mallocчасто возвращает блок, превышающий запрошенный. В лучшем случае программист мог передать размер, запрошенный в malloc()вызове, что совершенно не помогло бы разработчику free().
Ben Voigt

Ответы:


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-х годах способность C обрабатывать строки как простуюchar *фактически считалось определяющим преимуществом по сравнению с такими конкурентами, как Pascal и Algol .) Кроме того, strdupэта проблема не только в нем , но и во всех системных или пользовательских функциях, которые выделяют динамическую память.

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

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

И чем больше кода вы напишете с использованием этих новых функций, тем круче они кажутся. Мало того, что они делают ваш код легче писать, они также делают код быстрее - две вещи , которые не часто идут вместе! Раньше вы передавали их size_tповсюду, что увеличивало нагрузку на ЦП для копирования и означало, что вам приходилось чаще проливать регистры (особенно для дополнительных аргументов функции) и тратить впустую память (поскольку вызовы вложенных функций часто приводят к в нескольких копиях 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 - уверен, что он немного заработал на себе - - возвращается к возням с лебедой . Отправим его!

По крайней мере, так могло случиться.


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, он пытается (более или менее) быть переносимым и, таким образом, освобождает программиста от этой значительной нагрузки. Вы можете достичь этого уровня в любом случае другими способами (например, встроенной сборкой), но ключом является абстракция. Я согласен с этим ответом.
Marco A.

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

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

6
@Mehrdad: Нет никакой пользы от всей этой дополнительной работы, которую вы только что упомянули. Кроме того, передача sizeпараметра в freeоткрывает еще один источник ошибок.
Michael Foukarakis

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 в этих случаях делать функция?

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

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

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

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

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

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

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


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

4
«Потому что это не нужно» - это могло быть так же хорошо спроектировано так, что оно действительно нужно, не сохраняя эту информацию.
user253751

1
Что касается вашего пункта 2, вам интересно, определено ли поведение free (NULL). Ага, «Все совместимые со стандартами версии библиотеки C рассматривают свободный (NULL) как
запретный

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взять размер либо (а) полностью бесполезно, поскольку распределитель памяти не может полагаться на переданный размер для соответствия фактически выделенному размеру, либо (б) бессмысленно требует, чтобы пользователь выполнял работу, отслеживающую реальный размер, который будет легко обработан любым разумным менеджером памяти.


№1 правда. # 2: не понимаю, что вы имеете в виду. Не уверен в № 3, выравнивание не требует хранения дополнительной информации. # 4 - рассуждение по кругу; диспетчерам памяти требуются только эти накладные расходы для каждого блока, потому что они хранят размер, поэтому вы не можете использовать это в качестве аргумента, почему они хранят размер. А № 5 очень спорен.
user541686

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

2
@jaymmer: Да, это преждевременно. Я предлагаю подождать больше дня или двух, прежде чем соглашаться, и я предлагаю подумать об этом самостоятельно. Это действительно интересный вопрос, и большинство / все ответы, которые вы сначала получите на любой такой вопрос «почему» в StackOverflow, будут просто полуавтоматическими попытками оправдать текущую систему, а не фактически обратиться к основному вопросу.
user541686

@Mehrdad: Вы неправильно поняли то, что я говорю в №4. Я не говорю, что для этого потребуется дополнительный размер, я говорю, что (а) он переместится, кто должен хранить размер, поэтому на самом деле это не сэкономит места и (б) результирующее изменение, скорее всего, сделает его менее эффективно не более. Что касается пункта 5, я не уверен, что он вообще спорен: мы - самое большее - говорим о сохранении пары инструкций из бесплатного звонка. По сравнению со стоимостью бесплатного звонка это будет ничтожно мало.
Джек Эйдли

1
@Mehrdad: О, и на # 3 нет, это не требует дополнительной информации, это требует дополнительной памяти. Типичный диспетчер памяти, предназначенный для работы с 16-байтовым выравниванием, вернет указатель на 128-байтовый блок при запросе 115-байтового блока. Если freeвызов должен правильно передать размер, который нужно освободить, он должен это знать.
Джек Эйдли

5

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

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

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


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

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

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

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


Что касается operator delete, оказывается, что предложение N3778 2013 года («C ++ Sized Deallocation») предназначено для исправления и этого.


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


5
Для malloc()реализации это также устранит необходимость запоминать что-либо о выделенной области, уменьшив накладные расходы на выделение до накладных расходов на выравнивание. malloc()смогут вести всю бухгалтерию в освобожденных кусках. Есть варианты использования, в которых это было бы большим улучшением. Не рекомендуется выделять большой кусок памяти на несколько небольших, хотя это резко усилит фрагментацию.
cmaster - восстановить монику на работе

1
@cmaster: Те варианты использования были именно такими, о которых я говорил, вы попали в точку, спасибо. Что касается фрагментации: не уверен, как это увеличивает фрагментацию по сравнению с альтернативой, которая заключается в выделении и освобождении памяти небольшими фрагментами.
user541686

std::allocatorвыделяет только элементы определенного известного размера. Это не универсальный распределитель, сравниваются яблоки с апельсинами.
Джек Эйдли

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

1
@ Клиффорд: Именно поэтому я сказал, что для этого нет убедительной причины. Это просто было принято решение, и нет никаких оснований полагать, что оно строго лучше, чем альтернативы.
user541686

2

malloc и free идут рука об руку, причем каждому "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 (), он не будет перемещать данные, а возвращаемый указатель будет таким же, как оригинал. Это гарантированное поведение? Кажется, это обычное дело для нескольких встроенных распределителей, но я не был уверен, было ли такое поведение указано как часть standard-c.
rsaxvc

@rsaxvc: Хороший вопрос - документы cplusplus.com «Если новый размер больше, значение вновь выделенной части не определено». , подразумевая, что если он меньше, он определен . [opengroup.org () говорит: «Если новый размер объекта памяти потребует перемещения объекта, то пространство для предыдущего экземпляра объекта освобождается». - если бы он был меньше, данные не нужно было бы перемещать. Снова подразумевается, что меньшее не перераспределяется. Я не уверен, что говорится в стандарте ISO.
Клиффорд

1
reallocэто абсолютно разрешено перемещать данные. Согласно стандарту C это совершенно законно реализовывать reallocкак malloc+ memcpy+ free. И есть веские причины, по которым реализация может захотеть переместить выделение, размер которого был уменьшен, например, чтобы избежать фрагментации памяти.
Натаниэль Дж. Смит
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.