Как бесплатно узнать, сколько освободить?

385

В программировании на C вы можете передать любой указатель, который вам нравится, в качестве аргумента для освобождения. Как он узнает размер выделенной памяти для освобождения? Всякий раз, когда я передаю указатель на какую-то функцию, я также должен передать размер (то есть массив из 10 элементов должен получить 10 в качестве параметра, чтобы узнать размер массива), но мне не нужно передавать размер в свободная функция. Почему бы и нет, и могу ли я использовать эту же технику в своих собственных функциях, чтобы избавить меня от необходимости перемещаться вокруг дополнительной переменной длины массива?

Джошуа Чик
источник
Аналогичный вопрос: stackoverflow.com/questions/851958/… (хотя я бы сказал, что это не совсем дубликат)
Джон Картер
Система друзей - это еще один способ сделать это, который может вычисляться на основе указателя, без дополнительных затрат в каждом блоке.
EvilTeach
Этот пост объясняет это хорошо: stackoverflow.com/questions/1957099/…
Зеешан Махмуд

Ответы:

349

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

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

Гэри МакГилл
источник
44
К вашему сведению, например, BSD должен malloc_size()надежно получать доступ к размеру блока по malloc()указателю ed. Но нет надежного, портативного способа.
Лаалто
50
Я думаю, что важно сказать, что этот дополнительный информационный блок расположен перед возвращенным указателем.
Георг Шолли
39
@gs Ну, это зависит от реализации. Но да, вот где это обычно.
Фалаина
31
Можете ли вы представить себе ужас, если free()от программиста требуется точный отчет о размере malloc()блока? Утечки памяти достаточно плохи, как есть.
MusiGenesis
35
Почему эта информация доступна malloc()и free(), но вы должны хранить размер массива? Почему бы им не сделать что-то подобное, blockSize(ptr)если они все равно хранят информацию?
CorsiKa
144

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

Один типичный способ (in-line) состоит в том, чтобы фактически выделить как заголовок, так и запрошенную вами память с минимальным размером. Так, например, если вы запросили 20 байтов, система может выделить 48-байтовый блок:

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

Адрес, который вам дается, является адресом области данных. Затем, когда вы освободите блок, freeпросто возьмете адрес, который вы ему дали, и, предположив, что вы не заполнили этот адрес или память вокруг него, проверьте бухгалтерскую информацию непосредственно перед ней. Графически это будет выглядеть так:

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

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

Контрольные суммы и специальные маркеры, которые существуют в учетной информации, часто являются причиной ошибок, таких как «Память повреждена» или «Двойное освобождение», если вы перезаписываете их или освобождаете их дважды.

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


(а) Я написал реализации mallocво встроенных системах, в которых вы получили 128 байтов независимо от того, что вы запрашивали (это был размер самой большой структуры в системе), предполагая, что вы запрашивали 128 байтов или меньше (запросы на большее встретиться с нулевым возвращаемым значением). Очень простая битовая маска (т. Е. Не встроенная) использовалась, чтобы решить, был ли выделен фрагмент размером 128 байт или нет.

Другие, которые я разработал, имели разные пулы для 16-байтовых блоков, 64-байтовых блоков, 256-байтовых блоков и блоков размером 1 КБ, опять же с использованием битовой маски, чтобы определить, какие блоки были использованы или доступны.

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

paxdiablo
источник
@paxdiablo Означает ли это, что malloc не выделяет смежные блоки памяти?
user10678
2
@ user10678, единственное реальное требование mallocсостоит в том, чтобы он предоставил вам для успешного случая блок памяти, по крайней мере, такой же большой, как вы просили. Отдельные блоки являются смежными с точки зрения того, как вы получаете доступ к элементам внутри них, но не требуется, чтобы арены, из которых происходят блоки, были смежными.
paxdiablo
Смежный вопрос: почему нет варианта malloc / free, где вы указываете размер при освобождении, и поэтому он не должен хранить размер?
user253751
@ user253751, потому что тогда Theer одна более , что вам нужно следить, над и выше самого указателя. Это ненужное и опасное: void *x = malloc(200); free(x, 500);есть не кончится хорошо :-) В любом случае, для эффективности, фактического размером буфера может быть больше (вы просто не можете полагаться на это).
paxdiablo
@paxdiablo Это также позволяет избежать потери памяти для хранения размера.
user253751
47

Из comp.lang.cсписка часто задаваемых вопросов: откуда free знает, сколько байтов нужно освободить?

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

jdehaan
источник
2
Это не ответ. Вопрос как раз в этом: почему можно свободно и надежно искать размер блока, но пока программисту не доступна такая функция?
Бананач
Это действительно деталь реализации для API Malloc, и нет API, чтобы вернуть эту информацию стандартным способом (насколько мне известно). «Система» записывает это и использует это на free. Возможно, ответ вас не удовлетворит, но я не думаю, что вы получите ответ с более общей применимой информацией :-)
jdehaan
6

Этот ответ перемещен из Как free () знает, сколько памяти нужно освободить? где я был резко лишен возможности ответить дублирующим вопросом. Этот ответ должен соответствовать этому дубликату:


В случае mallocраспределителя кучи хранится отображение исходного возвращенного указателя на соответствующие детали, необходимые для freeпоследующего использования памяти. Обычно это включает в себя сохранение размера области памяти в любой форме, относящейся к используемому распределителю, например, необработанный размер, или узел в двоичном дереве, используемом для отслеживания распределений, или подсчет используемых «единиц» памяти.

freeне потерпит неудачу, если вы «переименуете» указатель или дублируете его любым способом. Однако ссылка не учитывается, и только первое freeбудет правильным. Дополнительные frees - это «двойные бесплатные» ошибки.

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

Мэтт Джойнер
источник
Я изменил значение указателя, возвращаемого вызовом malloc. И я освободил его без ошибок. Почему? Смотрите здесь: stackoverflow.com/questions/42618390/…
smwikipedia
4

На заметку о том, что библиотека GLib имеет функции выделения памяти, которые не сохраняют неявный размер, а затем вы просто передаете параметр size в free. Это может устранить часть накладных расходов.

Эфраим
источник
3

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

Больше информации по этому другому вопросу .

LiraNuna
источник
2
Они действительно зависят от библиотеки (обычно это библиотека C, которая обычно очень тесно связана с ОС). Для компилятора они просто функции.
Донал Феллоуз
2

Менеджер кучи сохранил объем памяти, принадлежащий выделенному блоку, где-то, когда вы вызывали malloc.

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

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

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

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

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

В общем случае ответ таков: для сохранения состояния выделяется отдельная структура данных.

DigitalRoss
источник
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;
MSalters
источник
Это метод, совершенно отличающийся от того, который использует BSD malloc для небольших объектов (хотя это совершенно хороший метод для создания массивов в стиле Pascal)
Пит Киркхем,
0

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

Варун Чхангани
источник
0

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

avish
источник