[vox-tech] Must one free() in C?

Jeff Newmiller vox-tech@lists.lugod.org
Wed, 8 May 2002 18:45:13 -0700 (PDT)


On Wed, 8 May 2002, Mark K. Kim wrote:

> On Wed, 8 May 2002, Jeff Newmiller wrote:
> 
> > On Wed, 8 May 2002, Mark K. Kim wrote:
> >
> > [...]
> >
> > > The impliciation of requiring calls to free() before
> > > exitting is enormous -- one would have to keep track of all the allocated
> > > memory before exitting from any error condition!
> >
> > Are you complaining? :)
> 
> If I understand you correctly, one must call free() on all allocated
> memory before exitting the program.  That means you can't exit simply
> because you ran out of memory because first you must *free* all memory
> first;  you can't exit because of a failed assertion test -- because first
> you must free all memory first; and you can't exit from a program because
> you got a signal that can't be handled any further -- because you first
> must free all memory first!  That's rediculous!

Ah, you _are_ complaining.  And, no, I don't think my position is
rediculous. (Naturally :)

However, some of your statements are sounding a bit thin.  "Can't exit
because you ran out of memory"... a low-level function definitely should
not do this.  It may propagate the error to a higher level, but it may not
pull the big lever.  "Can't exit because of a failed assertion test" ...
consider the environment under which assertion tests are allowed...
DEBUGGING.  They should disappear in production code... you should NEVER
use assertions to handle things like "out of memory" unless you can prove
that this cannot happen in production systems.  "can't exit from a program
because you got a signal that can't be handled any further" ... um, this
is environment specific, and if you are coding for a specific environment
that supports memory cleanup, then yes you can exit... but you better be
able to make similar assumptions about all those OTHER resources you are
deserting as well.

> If the above is true, I think the better question is "why aren't *you*
> complaing?"

Sometimes you can afford a global solution, and sometimes you can't.

An OS that can clean up after your program's memory usage is a nice safety
net, but as I said before, this is a general problem with resource usage.
Consider the case of buffered file output... failing to flush your buffers
before exiting can leave the files in an unusable state, and there is
nothing the system can do about _that_.

In a constrained resource embedded environment programmed in C (an
anticipated use of the C language from the Standard C authors' point of
view), the decision about how you can handle stranded resources depends on
where the optimal solution lies... and that may be in the application, or
it may be in the programming environment, depending on the
situation. 

> The system (whether that be the OS or the C library) obviously knows of
> all its memory and how they're allocated; if it didn't, free() wouldn't
> know how much memory to free when you call free() on a pointer.  So it's
> got a list of allocated memory *somewhere*, so why can't *it* handle
> freeing all the memory upon exit?  If freeing memory is *required*, then
> the system, designed to make the programmers' lives easier, should be the
> one doing the freeing, not the programmer!

You blithely suggest that the C library could be equivalent to an OS...
but if there is no OS, then the "system" could be anything from an
infinite loop to an MSDOS or something as capable as Linux.  As long as
you are free to assume the system can handle unfreed memory, you can write
your programs to assume that.  However, that is not scalable.

Most serious programs are composed of various nested levels of functions
organized in modules. Writing "programs" to run in an Operating System is
very much akin to writing dynamically linked functions in a very large
scale program.  I regard cleaning up after oneself as a necessary evil
that allows my functions to fit low in the hierarchy or high in the
hierarchy of a single process space.  If you depend on "die" in a
low-level routine, you restrict how deep you can bury it in one process
and still have a reliable system when you are done.

Deciding how many processes to use to accomplish a task is an optimization
step... having lots of processes is optimal for interactive and scripting
flexibility, but when you want to optimize your performance then getting
rid of superflous processes is a powerful step that requires functions
that don't "die" at the drop of a hat.

> If what you're saying is true, I'd be writing malloc(), calloc(), and
> realloc() wrappers -- the ultimate set of functions that keeps track of
> all the allocated memory and calls itself on exit() (using atexit()) to
> free all memory.  That's very generic, it's platform-independent, and
> there's no more or less overhead than a specialized one (for the most
> part).  The question then is, why doesn't the C standard say this easy
> thing can't be implemented by the system and has to be implemented by the
> programmer?

It does not say this thing "can't be implemented by the system".

It leaves this functionality UNDEFINED.  If you run in an environment
where this is taken care of, you are okay. If you don't, your program will
break the system.  If the system is a large server process containing your
functionality, then that means the server will be fragile. You can
probably tell in most cases whether you can assume this... but be aware
that this code will require reworking to fit in certain Standard C
environments.

I usually try to write my code in modules that can be re-used.  However,
if I am writing the main() routine, or if I am writing a script, I feel
free to use "die" and ignore freeing memory if the anticipated environment
doesn't need it.  The net result is that in almost all cases, memory usage
is covered anyway because the bulk of the functionality lies in the
functions. It may seem like I am doing more work than is necessary for my
lower-level modules, but the end result is code that can be plugged into
larger programs that may have different error reporting mechanisms (X,
MacOS, shell, ncurses, syslog, etc.) than the original smaller programs
had.  And memory usage is just one of those errors that happens, so
retaining flexibility to handle it as needed is usually worth it.

> --
> Mark "Jeff's-gotta-be-wrong" Kim
> [homepage address removed in fear for personal safety]
> PGP key available upon request to only closest relatives.

Hey... maybe I am... but I am not dangerous! :)

---------------------------------------------------------------------------
Jeff Newmiller                        The     .....       .....  Go Live...
DCN:<jdnewmil@dcn.davis.ca.us>        Basics: ##.#.       ##.#.  Live Go...
                                      Live:   OO#.. Dead: OO#..  Playing
Research Engineer (Solar/Batteries            O.O#.       #.O#.  with
/Software/Embedded Controllers)               .OO#.       .OO#.  rocks...2k
---------------------------------------------------------------------------