[Prev][Next][Index][Thread]

What to do when malloc() returns 0?



Currently, a 0 return from malloc() is considered a fatal
error throughout wwwlib. The error is reported to stderr,
and the process exits.

For some applications, this is unsuitable. A user may
be editing some data, and exiting in this manner will
cause the data to be lost. As long as we're writing
read-only tools like browsers, this isn't such a big deal.
But as we start building annotation and full editing
applications, it will become more and more of an issue.

The optimal behaviour of the library would be for all API
entry points that might call malloc() to include a failure
return mode. Unfortunately, this would require significant
re-engineering of the library -- not to mention the impact
on future work.

There is a precedent in the Xt library. It also considers
0 from malloc() a fatal error, but it provides a "hook"
function so that applications can do a non-local return
(longjump) up through the Xt code and back to the
application. The Xt spec doesn't guarantee that this
will work, and I'm sure some memory leaks would result
from this behaviour. Consider:

	XtFoo()
	{
		char *xxx = XtMalloc(100);
		XtBar();
		XtFree(xxx);
	}

	XtBar()
	{
		char *yyy = XtMalloc(200);
		XtFree(yyy);
	}

If the xxx alloc succeeds, but the yyy malloc fails, and
the XtError handler lonjumps up through XtFoo, xxx will
never be freed: memory leak.


The DCE programming API includes a longjump-based exception
implementation, so code can be written like this:

	DCEFoo()
	{
		char *xxx = malloc(100);

		if(!xxx) RAISE(OutOfMemory);

		TRY{
			DCEBar();
		}FINALLY{
			free(xxx);
		}
	}

	DCEBar()
	{
		char *yyy = malloc(200);

		if(!yyy) RAISE(OutOfMemory);
		free(yyy);
	}

That way, if the xxx alloc succeeds, even if DCEBar raises
and exception, xxx will be freed as the stack is unwound.

In summary, here are some proposals for what the library
code should do when malloc() returns 0:

	A. Fatal exit.
		+ easy to code
		- unsuitable for editing apps
		- unsuitable for DOS/Windows, other small-memory machines
		- makes the library unusable from otherwise robust
			applications

	B. Call fatal-exit hook function
		+ easy to code
		+ allows editing apps to do "last ditch save"
		+ allows graceful exit on small-memory machines
		- still unsuitable for integration with otherwise
			robust apps

	C. Raise an exception -- export exception API to library
			clients
		- requires significant library reengineering
		- requires integration of external technology
			(who's exception package do we use?)
		- requres library clients to use exceptions API
		+ allows robust editing applications to use the library

	D. Raise an exception, catch it before exiting the API,
		return an error code
		- requires significant library reengineering
		- requires integration of external technology
			(who's exception package do we use?)
		- requires distinction between "public" API and
			"internal" API
		- requires library clients to do lots of error checking
		+ suitable for use in robust applications

	E. Support "malloc failed" return code throughout the library
		- requires significant library reengineering
		- error prone
		- requires library clients to do lots of error checking
		+ suitable for use in robust applications


Enclosed is an interface I've used successfully to build applications.
Coding to this interfaces allows proposal A or B.
It allows all sorts of heap tracing (for the purify-less),
or none, and you can turn the tracing on and off by recompiling
just the .o file that implements mem_*(), without recompiling
the rest of the library.

It's used in stead of malloc(), free(), and strdup() from <stdlib.h>.

/* memalloc.h -- coding idioms for heap allocation
 * $Id: memalloc.h,v 1.3 1994/05/11 23:23:21 connolly Exp $
 */

#ifndef __memalloc_h
#define __memalloc_h

#include "stdclang.h"
#include <stddef.h> /* for size_t */

/* does NOT return 0 */
#define CNEW(type, annotation)     ((type*)mem_alloc(sizeof(type), 		      annotation,  __FILE__, __LINE__))

/* does NOT return 0 */
#define CNEWSIZE(type, qty, annotation)     ((type*)mem_alloc(sizeof(type)*(qty), 		      annotation, __FILE__, __LINE__))

/* does NOT return 0 */
#define CRESIZE(type, qty, old, annotation)     ((type*)mem_realloc(sizeof(type)*(qty), old, 			annotation, __FILE__, __LINE__))

/* does NOT return 0 */
#define STRDUP(str)     mem_strdup(str, "STRDUP", __FILE__, __LINE__)

/* null str allowed */
/* does NOT return 0 on allocation failure*/
#define ZSTRDUP(str)     mem_zstrdup(str, "ZSTRDUP", __FILE__, __LINE__)

#define DEALLOC(old)     mem_dealloc(old, __FILE__, __LINE__)

/* NOTE: this evaluates its arg more than once */
#define DISPOSE(old) do{ if(old){ DEALLOC(old); old = NULL; } }while(0)

extern void* mem_alloc PARAMS((size_t bytes,
				 CONST char *annotation,
				 CONST char* file, int line));

extern void* mem_realloc PARAMS((size_t bytes, void* old,
				   CONST char *annotation,
				   CONST char* file, int line));
extern void  mem_dealloc PARAMS((void* old,
				 CONST char* file, int line));

extern char* mem_strdup PARAMS((CONST char*,
				CONST char *annotation,
				CONST char* file, int line));

extern char* mem_zstrdup PARAMS((CONST char*,
				 CONST char *annotation,
				 CONST char* file, int line));

#endif /* __memalloc_h */

Follow-Ups: