LINUX.ORG.RU

Два философских вопроса по malloc/free

 , , ,


0

2

Здравствуйте. За месяц читания чужого Си-кода (и писания своего), меня не покидают два концептуальных вопроса про кучу

  1. Много где вижу конструкцию
    if(pth != NULL) free(pth);
    
    Несмотря на то, что в мане написано If ptr is NULL, no operation is performed. Откуда такое недоверие к манам?
  2. Каждый раз когда делаю malloc/free для локальных переменных (особенно если в цикле), в одном месте появляется неприятный зуд на тему «Память же фрагментируется. Нельзя часто выделять освобождать. Особенно много маленьких кусочков.» Оправдан ли этот зуд или все там норм с кучей?
★★★★★

Последнее исправление: beastie (всего исправлений: 1)

Откуда такое недоверие к манам?

Насколько я помню, free(NULL) описывается начиная с c89. То есть до c89 это UB. Возможно, что для поддержки компиляторов, которые поддерживают только ANSI C.

Хотя скорее всего потому, что просто не читали стандарт.

Deleted
()

Откуда такое недоверие к манам?

В манах на какую ОС? Windows, Linux, MacOS? Мало ли что учудят разработчики стандартной библиотеки и ОС, на которую надо перенести исходники.

grondek
()
Ответ на: комментарий от grondek

Откуда такое недоверие к манам?

В манах на какую ОС?

Это ман стандарта Си.

tailgunner ★★★★★
()

Зуд оправдан, частый malloc/free здорово тормозит программу. Но если не придумал другого алгоритма, пользуйся этим, не ломай. Фрагментация решается с помощью страничного выделения памяти в системе, как я понимаю. А лучше в программах с частым выделением/освобождением памяти пользуйся языками с GC. Или выделяй память размером с системную страницу (4K, пользуй её разными указателями, потом освобождай полностью).

Проверка на free(NULL) наверно сделана потому, что люди вручную обнуляют указатель после освобождения, чтобы не вызвать free ещё раз. Хотя это, конечно, бессмысленно.

Deleted
()
Последнее исправление: merhalak (всего исправлений: 3)
Ответ на: комментарий от Deleted

Часто такой код пишут в контексте:

if(p != NULL)
{
  free(p);
  p = NULL;
}
Чтобы повторно не вызвать free на одном и том же указателе, что приведет к крэшу.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 2)
Ответ на: комментарий от Deleted

А лучше в программах с частым выделением/освобождением памяти пользуйся языками с GC

Правильно я понимаю, что ЯП с GC откусывают кусок кучи и на нем реализуют свой malloc/free? Как же так получается, что в языках malloc/free хороший, а в самой системе - г**но?

makoven ★★★★★
() автор топика
Последнее исправление: makoven (всего исправлений: 2)
Ответ на: комментарий от Deleted

free(NULL) описывается начиная с c89

Но ведь никто никогда не вернется в 89 год )

makoven ★★★★★
() автор топика

Есть ещё философский вопрос, надо ли проверять результат malloc. Аргументы, что не надо, строятся на том, что ядро делает оверкоммит, т.е. возвращает тебе указатель на память, которой на самом деле нет, пока ты к ней не обратишься. И вот если ты обращаешься, а физической памяти для тебя нет, программа делает ОЙ, и никакие проверки на NULL у malloc тебя не спасают.

uuwaan ★★
()
Ответ на: комментарий от makoven

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

anonymous
()
Ответ на: комментарий от uuwaan

Так это если обратишься к области памяти, а если только проверит «адрес» этой памяти, то какие проблемы то?

anonymous
()
Ответ на: комментарий от uuwaan

Есть ещё философский вопрос, надо ли проверять результат malloc

Это не философский и не не впрос. Да, надо.

Аргументы, что не надо, строятся на том, что ядро делает оверкоммит

Меньше читай царску. писанину. Делает ядро оверкоммит или нет - зависит от настроек системы (и, как ни печально, all the world is not Linux).

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от uuwaan

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

Gvidon ★★★★
()
Ответ на: комментарий от invy

А еще лучше — вообще сделать макросы:

#define ALLOC(type, var, size)  type * var = ((type *)my_alloc(size, sizeof(type)))
#define MALLOC(type, size) ((type *)my_alloc(size, sizeof(type)))
#define FREE(ptr)  do{if(ptr){free(ptr); ptr = NULL;}}while(0)

void *my_alloc(size_t N, size_t S){
	void *p = calloc(N, S);
	if(!p) ERR("malloc");
	//assert(p);
	return p;
}
И тогда можно не париться насчет double free или проверки возвращаемого malloc'ом.

anonymous
()
Ответ на: комментарий от Gvidon

Никого я не призываю. Но это объективно есть: много софта и либ, которые не проверяют или работают по принципу malloc-or-die. В обоих подходах нехватка памяти считается чем-то весьма маловероятным, и программа в этом случае просто валится. Считаю, что о таких приколах надо знать.

P.S. Кстати, удивило, что в Go реализовали именно такой подход, нехватка памяти в этом языке как бы не существует и заваливает программу.

uuwaan ★★
()
Ответ на: комментарий от anonymous

А можно пример «знает, как именно ему удобнее выделять память»? А то как-то в толк не возьму о чем речь)

makoven ★★★★★
() автор топика
Ответ на: комментарий от int13h

Я опять затронул какой-то извечный философский вопрос? Ну, извините, раньше треды по сишке не часто посещал

makoven ★★★★★
() автор топика
Ответ на: комментарий от makoven

В языках с динамическим управлением памятью рантайм знает о памяти и указателях всё. Как следствие, он может, например, делать перемещение выделенных участков (сборщики мусора mark'n'sweep), что автоматически дефрагментирует память.

В языках (точнее, реализациях) типа CPython и Perl5 со счётчиками ссылок память и фрагментируется, и с мелким мусором они справляются почти столь же плохо, как системный malloc (а если и лучше, то опять же за счёт оптимизации под конкретные условия, типа выделения разных объектов в разных кучах, что malloc не может — он ничего не знает об объектах).

anonymous
()

Имеет смысл, если известно, что в большинстве случаев pth == NULL. Экономится вызов функции.

Deleted
()
Ответ на: комментарий от anonymous

перемещение выделенных участков (сборщики мусора mark'n'sweep), что автоматически дефрагментирует память

Понятно. Спасибо. Ну а если я работаю с кучей в пределах одной функции? Навыделял много маленьких кусочков, попользовал, сразу удалил. Ничего дефрагментировать вроде не надо. Или и тут есть подводные камни?

makoven ★★★★★
() автор топика

Несмотря на то, что в мане написано If ptr is NULL, no operation is performed.

По-моему, есть 2 причины для этого:
1. Большинство функций, удаляющих объекты, не следуют этому правилу, - им нельзя передавать нулевой указатель. Поэтому для единообразия делается проверка и перед free().
2. Компиляторы оставляют библиотечный вызов free(), тут не происходит оптимизаций, так что в случаях, если указатель вероятно нулевой, проверка без вызова функции будет производительней

Оправдан ли этот зуд или все там норм с кучей?

Современные аллокаторы достаточно эффективно работают с памятью, так что много маленьких кусочков - это не страшно.

Нельзя часто выделять освобождать.

Это смотря насколько часто. Если время полезной работы фрагмента программы сопоставимо со временем, потраченным на работу аллокатора, то да, нельзя. Если несопоставимо больше, то всё равно.

Sorcerer ★★★★★
()
Последнее исправление: Sorcerer (всего исправлений: 3)
Ответ на: комментарий от makoven

В ембеде, да, не смешно.

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

Sorcerer ★★★★★
()
Ответ на: комментарий от Deleted

пока не пришлось инструкции считать.

Если у тебя в цикле, где надо считать инструкции, стоит free, ты что-то делаешь не так.

tailgunner ★★★★★
()
Ответ на: комментарий от Sorcerer

Современные аллокаторы достаточно эффективно работают с памятью, так что много маленьких кусочков - это не страшно

Ну ок. Буду и дальше выделять.

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

Производительность не сильно волнует. Больше простота кода. И чтобы долгоработающая програма внезапно не грохнулась из-за сильной фрагментированности

makoven ★★★★★
() автор топика
Ответ на: комментарий от anonymous

типа выделения разных объектов в разных кучах

Типа куча для больших и куча для маленьких?

makoven ★★★★★
() автор топика
Ответ на: комментарий от anonymous

И тогда можно не париться насчет double free

Сюрприз - на одну область памяти может указывать несколько указателей.

red75prim ★★★
()

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

it depends... от реализации стандартной библиотеки. Помнится в какой-то версии для оффтопик за malloc-ом лежал один большой буфер(при инициализации выделялся одним системным вызовом), а malloc откусывал от него сколько нужно(а free возвращал).

Откуда такое недоверие к манам?

А ещё потому-что возможно случайно нарваться на нежданчик

#define malloc MySuperAlloc
#define free MySuperFree
Шутка. Адекватный оптимизатор такую паранойю простит.

FeyFre ★★★★
()
Ответ на: комментарий от makoven

Прости, шлюха уже тут. Слишком сильно я этих собак кормил дерьмом.

anonymous
()
Ответ на: комментарий от makoven

Всё зависит от того кем ты являешься. Домохозяйкой, которая не владеет поведением своей программы - да. Если это не так - у тебя такого вопроса не возникнет. Обладаешь ты полной властью над поведением своей программы? Ответ на этот вопрос и будет ответом на твой.

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

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

Если твоя аллокация меньше 4кб, то проверка всегда не имеет смысла, ибо вернуться null там не может никогда.

anonymous
()
Ответ на: комментарий от red75prim

Это уж проблема того, кто эту толпу указателей развел!

anonymous
()
Ответ на: комментарий от anonymous

Адресс всегда будет - толку его проверять? Памяти в нём не будет.

anonymous
()

внезапно:

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

2. для мелких частых однотипных объектов частенько стоит делать пулы. Это управляемо и быстро.

MKuznetsov ★★★★★
()
Ответ на: комментарий от Deleted

А лучше в программах с частым выделением/освобождением памяти пользуйся языками с GC

Чот в голосину.

Или выделяй память размером с системную страницу (4K, пользуй её разными указателями, потом освобождай полностью).

А лучше напиши уже свой аллокатор, который всё это непотребство скроет с глаз долой.

Dark_SavanT ★★★★★
()
Ответ на: комментарий от anonymous

Что мешает знать программисту, когда ему удобнее выделять память?

CatsCantFly
()
Ответ на: комментарий от anonymous

Когда уже си осилишь и перестанешь писать на С/С++?

Макрос фрии вообще не имеет смысла. Если у тебя где-то есть даблфри - твой код дерьмо и сокрывать это неработающими кастылями глупо.

anonymous
()
Ответ на: комментарий от Dark_SavanT

А на тех где не вернёт - твоя куллпрога упадёт. Отличная история.

На днищеплатформах проще сделать обёртку над маллоком с ordie - всё равно они нахрен никому не нужны. Да и вообще «кросс-платформенное» никому нахрен не упало. Всё «кросс-платформенное» - это гуйня для домохозяек, а её на си не пишут.

anonymous
()
Ответ на: комментарий от anonymous

Что за пургу ты насчет C/C++ несешь? Пишу на сях. Нормальных сях.

Макрос фрии вообще не имеет смысла.

Очень даже имеет, потому как, скажем, если сделать просто free(list), когда освободили последний член, то последующее добавление члена в список будет UB!

Царь, ты уже всех достал своим тупым бредом!

anonymous
()
Ответ на: комментарий от makoven

Больше простота кода.

тогда везде замена free( на freemakoven(

где

freemakoven(p)
   *p
{if(p)free(p)}

али макрос(ежели время ценнее памяти.)

qulinxao ★★☆
()
Ответ на: комментарий от anonymous

Упала - туда и дорога. OOM если что вообще не спросит.

Dark_SavanT ★★★★★
()
Ответ на: комментарий от anonymous

как обычно даблфри можно избежать дополнительным уровнем косвенности

со своей таблицей соответствия твой_управляемый_хэндл<->железко_адресс и сопуствиующее этой табличке различные хинты для более_лучшего обмазывания памятью

-и вуаля можно ужо и мусор собирать али ещё чё.

qulinxao ★★☆
()
Ответ на: комментарий от anonymous

Очень даже имеет, потому как, скажем, если сделать просто free(list), когда освободили последний член, то последующее добавление члена в список будет UB!

Редко случается ситуация, когда на какое-то место в памяти указывает только один указатель.

char *a = MALLOC(char, 10), *b = a;
FREE(a);
FREE(b); //oops

А вообще, мне не понятно, зачем ТС динамическая память, если он её убивает в конце функции. Пусть использует стек или ареновый/пуловый аллокатор. Основная цель malloc (если не думать, что размер стека ограничен) — это продление времени жизни созданного объекта.

vzzo ★★★
()
Ответ на: комментарий от anonymous

Что за пургу ты насчет C/C++ несешь? Пишу на сях. Нормальных сях.

Действительно, прям как в той тут Несколько вопросов новичка по Си (комментарий)

Хорошо, зачем ты кастишь руками воид, который кастить руками надо только в С/С++?

Очень даже имеет, потому как, скажем, если сделать просто free(list), когда освободили последний член, то последующее добавление члена в список будет UB!

Чё? Куда чё добавлять? Ты код-то писать нормально не можешь, чтобы не добавлять куда не надо?

По поводу твоего фри.

int * ptr = malloc(100500); int * ptr2 = ptr + 100; free(ptr); ptr2 - hello, alesha.

Такая же жопа с перебачей в функции, да и везде.

anonymous
()
Ответ на: комментарий от Dark_SavanT

А чё в голосину то? Выделяется огромный массив, используется с достаточно большой фрагментацией, потом используемые элементы переносятся в другой массив, а этот перевыделяется/очищается. Да, жрёт дохрена оперативки, дохренища. Но я не вижу особого смысла писать собственный аллокатор, если в Java/Go/Erlang по факту занимаются написанием как раз этого самого аллокатора профессионально.

Deleted
()
Последнее исправление: merhalak (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.