I'd imagine the main way one reduces instances of these mistakes is to restrict resource ownership into certain patterns which have a clear place for freeing, and rules that ensure it's always reached, and only once.
There are many approaches depending on the type of program or suite of programs being built.
Always pairing the creation of free() code and functions with every malloc() is one discipline.
Another, for a class of C utilities, is to never free() at all .. "compute anticipated resource limits early, malloc and open pipes in advance, process data stream and exit when done" works for a body of cases.
In large C projects of times past it's often the case that resource management, string handling, etc are isolated and handled in dedicated sub sections that resemble the kinds of safe handling methods baked into modern 'safe' languges.