Как решается эта задача фактически? Есть ли разные подходы к её решению в разных ОС?
Управление "кучей" - самая темная сторона языка C (точнее, не самого языка, разумеется, а его стандартной библиотеки). Даже в библиотеках разных компиляторов для одной ОС эти функции могут быть реализованы по-разному. Более того, непредсказуемость скорости их выполнения затрудняет их использование в критичных ко времени отклика приложениях. Например, популярная операционная система реального времени для микроконтроллеров FreeRTOS вовсе отказывается от использования библиотечных
malloc() и
free() и заменяет собственными аналогами (на самом деле там их целый набор, поскольку ни одна реализация не идеальна).
Хочу поинтересоваться ... : malloc(), когда выделяет память делает это кратно страницам или нет.
В известных мне реализациях - нет. Тем более что логическое адресное пространство зачастую линейно (если не рассматривать аномальные варианты вроде сегментированной памяти 8086 или совсем уж приподвывернутого страничного доступа к памяти 8051 или PIC), и в этом нет реальной необходимости. Поэтому на "обрезки" много памяти не тратится, максимум - на выравнивание блока по границе слова.
free() не сможет освободить объём полноценно, и, в итоге, всё превратится в фрагментированную кучу.
Это - типичные трудовые будни любой программы C, использующей динамическое создание объектов, если только мы имеем дело не с выхолощенным случаем освобождения блоков в порядке, обратном созданию. Как упоминалось, детали реализации разнятся, но в целом очень грубо картина выглядит так (картинки для экономии рисовать не буду, их и так полный интернет).
Фрагментированная куча содержит связный список свободных областей. Очередной вызов
malloc() пытается найти наиболее подходящую по размеру и выделить ее. Если не удается, отрезает кусок от большей, остаток возвращает в список свободных. Соответственно
free() возвращает область в список свободных и пытается слить со смежными, если это возможно. Есть более изощренные стратегии, но на каждую
хитрую ж из них найдется случай
с винтом, когда ее эффективность ниже, чем у самой наивно-прямолинейной. Поскольку последовательный поиск в связном списке - операция с непредсказуемым временем выполнения, о реальном времени придется забыть. Ну а если реального времени (и вообще запредельной эффективности) от программы не требуется, имеет смысл забыть про сам C и взять что-нибудь с более высокоуровневой моделью динамического выделения памяти, например, Java или C#.
P.S. Кстати, не вижу никакого криминала, если в подобных случаях создавать новые темы. Тонкости работы с "кучей" IMHO гораздо интереснее первоначального вопроса темы, а найти их в данном случае будет гораздо труднее.