The main reason that eg C programs don’t suffer so much from heap fragmentation is that writing a C program which uses ephemeral objects in the way a GC’d language might is so incredibly painful that one mostly avoids the convenient programming techniques which lead to fragmentation.
The usual solution in a language like C (or a safe language like rust) to data where it is hard to know when it should be freed is to use reference counting yet reference counting is typically slower than an actual gc, especially on a multithreaded system (but this needs a quite advanced gc) where reference counting needs atomics. This leads some C programs to use gc when allocating and tracking these objects because it is cheaper than the overhead of malloc and reference counting.