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.
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.