It is possible and in fact not particularly hard to have a fully conformant C implementation that has defragmentable heap. Many implementations are possible, and some were even supported by widely available consumer hardware.
On x86, 16-bit C code built in large mode (multiple code and data segments, objects limited to 64kb length) could be made to run in 286 protected mode directly.
With changes only to the C runtime library, but not compiler itself, the segments could be used as identifiers either for memory arenas (for small objects), or for objects themselves (for larger objects that each had their own "arena"). The allocator maintained a couple arenas for "small" objects, and anything that didn't fit in those got its own segment descriptor.
Windows 3.x couldn't quite do it that way, since there was no segment descriptor indirection on 8086. Thus, Windows heap required explicit handles and object pinning in place of indirection. In any case, the segment descriptor cache misses and churn were expensive enough that it was better that Windows did it this way back then, as the performance hit in some cases was substantial.
If you could trade off some performance for not running out of heap due to fragmentation, then using segment descriptors as an indirection layer to allow moving objects around in the heap was a reasonable idea. It also facilitated paging infrequently used objects to disk, even on 286. There was a small software shop somewhere that made a substitute runtime for Borland products (Pascal and C) that did memory allocation this way on 286, paging included. It was a niche product as far as I remember, and I have no recollection of how it was called, who made it, etc. :(