[comp.lang.c] Question on Portable Memory Management Syntax

esink@turia.dit.upm.es (Eric Wayne Sink) (02/18/91)

Here's a question for everyone.  It's kind of subjective, so all
opinions welcome.  Please respond by posting (unless the question is
inappropriate) as I usually do read this newsgroup. :-)

Given : There is a piece of software which must be portable between
the Macintosh, and some more generic platform.  For those of you
unfamiliar with programming on the Mac, the following will explain the
constraint of the problem :
   The Mac memory manager performs an order of magnitude better if
   most or all dynamically allocated blocks of memory are allocated as
   'handles' instead of pointers.  A handle is pointer to a master
   pointer.  The Memory manager reserves the right to move your block
   of memory at well defined times.  When it does so, the master
   pointer is changed.  For this reason, most programs which allocat
   lots of memory at runtime look like this on the Mac :
	
	struct mystruct **theHandle;
	theHandle = NewHandle(sizeof(struct mystruct));
	(*theHandle)->someMember = whatever;

   instead of the usual :

	struct mystruct *thePointer;
	thePointer = malloc(sizeof(struct mystruct));
	thePointer->someMember = whatever;

The crucial issue here is finding a syntax for referencing struct
members.  This syntax must be usable on both platforms.
The piece of software in question allocates a LOT of
memory at run time.  In order to handle portability as described
above, I see 3 possible solutions, each with advantages and
disadvantages :

     1.  Always use malloc.  This will result in HORRIBLE performance
     on the Macintosh, so I consider this option out of the question.
     Please, no flames about the Mac Memory Manager.

     2.  Always use handles.  This is done by implementing a simple
     version of the Mac memory manager by using malloc. Each call to
     NewHandle allocates the correctly sized block with a malloc call,
     and allocates a master pointer, also with malloc, setting
     that pointer to point to the block, and returns the address of
     the master pointer.  (Of course, this memory manager is not going
     to go moving blocks around like the Mac does.)  Disadvantages here
     are that every struct reference potentially involves an extra
     redirection on a platform which does not need it.  An advantage
     is that I would probably not merely allocate a master pointer,
     but would allocate a small struct containing the master pointer
     as well as the size of the block, so better memory usage
     statistics could be kept.  An example implementation of a very
     simple NewHandle :

          void **NewHandle(long size)
          {
            void **result;
            result = malloc(sizeof(void *));
            *result = malloc(size);
            return result;
          }

     3.  Use the preprocessor to allow usage of the best method for
     each platform.  In this case, we do something like the following :

          #ifdef MAC
	  #define STRUXMEMBER(s,m) ((*s)->(m))
	  typedef struct mystruct **BLOCKHP;
	  #else
	  #define STRUXMEMBER(s,m) ((s)->(m))
	  typedef struct mystruct *BLOCKHP;
	  #endif

     Then, every struct reference looks like this :

	BLOCKHP GetMemoryBlock(long size);
        /* The definition of GetMemoryBlock has #ifdef's to return the
	correct type. */

	BLOCKHP theRef;
	theRef = GetMemoryBlock(sizeof(struct mystruct));
	STRUXMEMBER(theRef,someMember) = whatever;

Option 2 introduces inefficiency into the program.  Option 3
introduces undesirable syntactic changes to the source.  Which is
better ?  Is the method of option 3 too ugly ?  Are there compromises
worth considering ?

Any and all comments appreciated.  


Eric W. Sink                     | Putting the phrase      |All opinions
Departamento de Telematica       | "Frequently Asked"      |are mine and
Universidad Politecnica de Madrid| in your kill file is    |not necessarily
esink@turia.dit.upm.es           | not recommended.        |yours.

gwyn@smoke.brl.mil (Doug Gwyn) (02/20/91)

In article <1991Feb18.133400.11870@dit.upm.es> esink@turia.dit.upm.es (Eric Wayne Sink) writes:
>   'handles' instead of pointers.

Yes, Apple uses these on the IIGS also.  Generally such an approach
permits "garbage collection" and compaction of the arena by the
memory allocation subsystem.

Unless you are willing to implement and maintain your own garbage-
collecting storage allocator (which from practical experience I
would recommend against except in cases where it really is needed),
I would suggest that you define a common interface that takes the
desired type and a pointer to the variable into which the handle/
pointer is to be put, rather than mallocing the handle variable.
I.e.

#if MAC
/* *Handle() should be declared here, probably by some MPW header */
#define	Via(ap)		(*(ap))
#define	MyAlloc(t,ap)	(((ap) = (**(t))NewHandle(sizeof(t))) != NULL)
#define	MyFree(ap)	DisposeHandle((void **)(ap))
#else
/* malloc() and free() should be declared here, perhaps via <stdlib.h> */
#define	Via(ap)		(ap)
#define	MyAlloc(t,ap)	(((ap) = (*(t))malloc(sizeof(t))) != NULL)
#define	MyFree(ap)	free((void *)(ap))
#endif
...
	struct mystruct *Via(theHandle);
	if ( MyAlloc( struct mystruct, theHandle ) )
		Via(theHandle)->someMember = whatever;

If "Via" has too many characters for your taste, feel free to use
a shorter macro name.  MyAlloc() could obviously be designed to
have a different interface more like traditional malloc(), but I
found it convenient to package the test for allocation failure
into the macro.