[comp.lang.c] reducing external identifier collisions

gwyn@smoke.brl.mil (Doug Gwyn) (11/10/90)

In article <24964@adm.BRL.MIL> nick@spider.co.uk (Nick Felisiak) writes:
>Ideally, the only thing visible by name is the streamtab structure which
>defines the entry points to the driver.  In practice, if a driver is
>constructed from more than one source file, a potentially large number
>of names remain visible simply because there is no type of name which
>disappears after an 'ld -r' used to produce the single driver object file.

This is a nice example of a real-world problem with the single, flat
external identifier name space.  The "ld"/COFF hackery attempts to
solve these problems by in effect introducing levels into the name
space.  There is another approach, using standard C facilities, that
we have used to good effect in a large project where tight control
over name space was a major software engineering consideration.

A project-wide header file describes the SOLE allowed externally-
visible object for any of numerous potential "methods" (each developed
according to project rules, but without knowledge of other "methods"):

	/*
		<Dd.h> -- MUVES "Dd" (data dependencies) package definitions

		This header is part of the "universal" part of the project,
		available for use by all developers of methods.

		DdEntry is a definition for the structure type used to package
		the sole entry point to a method.
	*/
	/* ... */
	typedef struct
		{
		pointer		method;		/* -> private method data */
		int		info_needed;	/* request flags for RrShoot() */
		bool		ctx_dependent;	/* FRF depends on context */
		float		default_frf;	/* unhit component's FRF value */
		bool	(*meth_init)( const char *, const char * );
		bool	(*view_init)( DqNode * );
		bool	(*shot_init)( const RrRay * );
		bool	(*cont_init)( const char *, const char * );
		void	(*meth_term)( void );
		void	(*view_term)( void );
		void	(*shot_term)( void );
		void	(*cont_term)( void );
		}			DdEntry;

	/*
		DdApprox is a copy of the entry-point structure that contains
		method-specific initialization/termination function pointers
		and globally-accessible data for the method.

		It is set to the proper value at run time after the user has
		specified which method to use.  This is used by MUVES code
		outside all method implementations when making standard
		"requests" to the currently selected method.
	*/
	extern DdEntry DdApprox;

Then, each "method" is allowed to use the ONE global symbol "DdXXX", where
"XXX" is actually replaced by the method's name.

	/*
		compart.h -- "compart" method private definitions
	*/
	#include <Dd.h>		/* for DdEntry */
	/* ... */
	/*
		Private external functions and data must be accessed via the
		sole external hook for the "compart" method, Ddcompart, which
		contains a method-specific pointer as its first member.  In
		the "compart" method's case, this is a pointer to an XxEntry
		structure, which contains XxBypass and pointers to functions
		defined in the method's support module compart.c.  (There are
		numerous other separate modules implementing the bulk of the
		method.  They communicate with each other solely through the
		hooks provided in Ddcompart.)

		Note that in code for this method we can refer directly to
		Ddcompart; no need to go through the copy in DdApprox since we
		KNOW what method must be in effect for all code using this
		"compart" method-private header.
	*/
	typedef struct
		{
		bool		bypass;		/* special testing flag */
		/* The following are used by various "compart" modules: */
		bool		(*hit)( RrTrace *trace );
		/* ... */
		}	XxEntry;

	/*
		Ddcompart is the structure that contains shared globally-
		accessible data and entry points for the "compart" method.
	*/
	extern DdEntry Ddcompart;

	/*
		bool	XxBypass

		XxBypass is a flag that is set to true during the module test
		programs, in order to disable use of "Dd" package functions.
	*/
	#define	XxBypass	((XxEntry*)Ddcompart.method)->bypass

	#ifndef XxNOFNDEFS	/* (define XxNOFNDEFS for compart.c only) */
	/*
		bool	XxHit( RrTrace *trace )

		XxHit() records a true hit flag for the current component
		(indicating that the component was present on the
		threat path and was perforated) and returns true 
		upon succcess.  If XxHit() fails, false is returned.
	*/
	#define	XxHit	((XxEntry*)Ddcompart.method)->hit
	/*...*/
	#endif	/* XxNOFNDEFS */

Identifiers beginning with "Xx" are guaranteed to not be used by any of the
standard project headers (apart from private method-specific headers), so we
can be sure that symbols starting with "Xx" will not collide with anything
within a single translation unit, as used here.  Note that there are NO
external identifiers starting with "Xx".

The "compart" method's support functions corresponding to the preceding header
are all contained in:

	/*
		compart.c -- "compart" method shared data and functions
	*/
	#include <Dd.h>
	/* ... */
	#define	XxNOFNDEFS	/* permit definition of "Xx" functions below */
	#include "compart.h"
	#undef	XxNOFNDEFS

	/*
		XxHit() records a true hit flag for the current component
		(indicating that the component was present on the
		threat path and was perforated) and returns true 
		upon succcess.  If XxHit() fails, false is returned.
	*/
	static bool
	XxHit( RrTrace *trace )
		{
		/* ... */
		return true;
		}

	/* Dd-invoked global initialization and termination functions: */

	/*
		XxContInit() is the per-context Dd init function which is
		called by AtUtility().  This function returns true unless
		an error occurs.
	*/
	static bool
	XxContInit( const char *mission, const char *envir )
		{
		/* ... */
		return true;
		}
	/* ... */

	/*
		Private external functions and data must be accessed via the
		sole external hook for the approximation method, Ddcompart,
		which contains a method-specific pointer as its first member.
		In our case, this is a pointer to an XxEntry structure, which
		contains XxBypass and pointers to functions defined in
		compart.c.
	*/
	static XxEntry	XxItems =
		{
		false,				/* XxBypass lives here! */
		XxHit,
		/* ... */
		};

	/*
		Ddcompart is the structure that contains globally-accessible
		data and entry points for the "compart" method, as described
		previously.
	*/
	DdEntry Ddcompart =	/* "compart"-specific hook for DdApprox */
		{
		(pointer)&XxItems,	/* method's private pointer */
		RrLOCATION | RrNORMAL,	/* flags for RrShoot() */
		true,			/* FRF depends on context */
		1.0,			/* FRF for unhit component (LOF=0) */
		XxMethInit,		/* approx-method initialization func */
		NULL,			/* per-view initialization func */
		NULL,			/* per-shot initialization func */
		XxContInit,		/* per-context initialization func */
		XxMethTerm,		/* approx-method termination func */
		NULL,			/* per-view termination func */
		NULL,			/* per-shot termination func */
		NULL			/* per-context termination func */
		};

The actual MUVES code is considerably more elaborate that the streamlined
extracts I have provided here.  My point in posting this is to illustrate
a practical application of the idea of encapsulating multiple items within
a single (possibly hierachically-structured, as in this example) externally-
visible object, so that a large number of items can be accessed without
using up a large number of external identifiers.  A MUVES "method" may be
implemented as many thousands of lines of source code split among hundreds
of separate source files, yet only a single external identifier is needed
for the entire method.