(a) Du kannst in deine Binaries reinschreiben, wieviel Speicher sie maximal belegen dürfen, und reservierst dann den entsprechenden Speicher beim Programmstart vor. Das ist der klassische UNIX-Ansatz, der die Speicherverwaltung auf den BRK/SBRK-Syscalls aufbaut. Wenn hinter dem Programm noch Platz ist, kannst du das Datensegment (und damit den Heap) auch dynamisch vergrößern. Nachteil: Die meisten Programme verschwenden Speicher, und Programme werden - je nach Input - sterben. (Der Stack nutzt übrigens zwingend diesen Ansatz.)
(b) Der Heap muss aber nicht an einem Stück vorliegen und darf damit auch über mehrere Segmente verteilt sein. Du reservierst einfach eine gewisse Menge an Speicher und wenn das Programm zusätzlichen Speicher mittels malloc() anfordert, dann beschaffst du einfach ein zusätzliches Segment mit mehr Speicher. Nachteil: Plötzlich sind deine Pointer nicht mehr linear, sondern bestehen aus Adresse und Segmentnummer. Damit kommt so gut wie kein moderner Compiler klar. (Vielleicht Watcom C/C++?)
(c) Wer braucht schon Speicherschutz für den Heap? Du legst ein globales Segment für den Heap an, und beantwortest alle Anfragen aller Prozesse aus diesem Segment (Memory Pool). Linux auf NOMMU-Architekturen (uClinux) macht das so, um möglichst wenig Speicher zu verschwenden. Nachteil: Wenn dein Heap zu sehr fragmentiert, kannst du nichts dagegen tun.
Dynamischer Speicher ist dynamisch, und damit anwendungs- und datenabhängig. Mit Segmentierung allein wirst du keine gute Lösung mit Speicherschutz bekommen können, solange du nichts über deine Anwendungen weißt.
Beispiel: Speicherfragmentierung auf eingebetteten Systemen ist meist kein Problem, weil Prozesse in der Regel nur beim Systemstart gestartet werden und der Speicherverbrauch relativ statisch ist (solche Systeme sind oft kritisch und dürfen daher keinen out-of-memory wichtiger Dienste erleben und sind daher entsprechend aufgebaut).