[net.lang.c] strings in defs.h force recompiles when changed.

jerry@oliveb.UUCP (Jerry Aguirre) (10/30/84)

It is fairly common practice when writing large programs in C to
break them into individually compiled files.  To provide for easy
maintenance of these files "constants" are declared in a common include
file hereafter referred to as "defs.h".

The usenet news software is a good example.  It's defs.h file contains
"constants" such as:

	#define NEWS_VERSION   "B 2.10.2 9/5/84"
	#define DFLTSUB "general,all.general"
	#define TMAIL	"/usr/ucb/Mail"
	#define	ADMSUB	"general,all.announce"
	#define PAGE	"/usr/ucb/more"
	#define NOTIFY	"usenet"
	#define DFTXMIT	"uux - -r -z %s!rnews < %s"
	#define UXMIT	"uux -r -z -c %s!rnews '<' %s"
	#define DFTEDITOR "vi"

One purported reason for localizing these strings into a single place is
to make them easy to change.

There is one problem with this scheme.  Change just one string, that
might only be used in one module, and the makefile will force the
recompilation of every damn module in the package!  This negates most
of the advantage of using make(1) as the most commonly modified items
force the recompilation of everything.  You might as well use a shell
script to compile everything as most of the time there will be no
selective recompilation.

I have been experimenting with an alternate scheme that, while more
complex to set up, has several advantages.  This scheme splits the
information into two files hereafter referred to as "defs.h" and
"strings.c".  The strings.c file looks like:
	char *strings[] = {
	    "B 2.10.2 9/5/84",
	    "general,all.general",
	    "/usr/ucb/Mail",
	    "general,all.announce",
	    "/usr/ucb/more",
	    "usenet",
	    "uux - -r -z %s!rnews < %s",
	    "uux -r -z -c %s!rnews '<' %s",
	    "vi"
	};

And the defs.h file now looks like:
	extern char *strings[];
	#define NEWS_VERSION	strings[0]
	#define DFLTSUB		strings[1]
	#define TMAIL		strings[2]
	#define	ADMSUB		strings[3]
	#define PAGE		strings[4]
	#define NOTIFY		strings[5]
	#define DFTXMIT		strings[6]
	#define UXMIT		strings[7]
	#define DFTEDITOR 	strings[8]

The rest of
the modules are unchanged in their usage of the defines.
The makefile includes a new module strings.o.  It is just as easy to
edit strings.c as defs.h so modification is no harder.  The advantages are:
    1 - Changes do not force recompiling every module.  Change the
	default news group and only strings.c gets recompiled.
    2 - Promotes the use of a single copy of each string.  If you have
	five places in the program where it does a:
	    strcpy(buf, DFLTSUB);
	you still only use one string.  The old defs.h would result in
	five copies of the string.

The only deficiencies that I can see are:
    1 - The strings involved must be "read only" as there is only a
	single copy of them.  This is usually the case and results in
	advantage 2 above.
    2 - The defs.h and strings.c files must track each other.  New
	strings can be easily added to the end but if a string is
	removed the offsets in the defs.h file must be changed to match.
	It is probably easier to just use a zero entry in the strings
	array and keep the numbering the same.  If the numbering did get
	off the results could be pretty bizarre.

I usually extend the strings array to include common strings such as:
	"Can not open \"%s\"",
	"Can not create \"%s\"",
	"Can not read from \"%s\"",
	"Can not write to \"%s\""

A more general solution should probably use this scheme for other
constants.  One could have a "const.h" file that had strings[],
integers[], floats[], etc.  The problem is that most integer defines
are used at compile time in array size definitions and switch cases.

Any opinions on the problem or the solution?

					    Jerry Aguirre
{hplabs|fortune|idi|ihnp4|ios|tolerant|allegra|tymix}!oliveb!jerry

garys@bunker.UUCP (Gary M. Samuelson) (11/01/84)

Jerry Aguirre doesn't like

> file defs.h:
> 	#define NEWS_VERSION   "B 2.10.2 9/5/84"
> 	#define DFLTSUB "general,all.general"
> 	...

(and I agree with his reasons), and suggests two files, defs.h
and strings.c, where strings.c defines an array of pointers
to strings such as:

> 	char *strings[] = {
> 	    "B 2.10.2 9/5/84",
> 	    "general,all.general",
>	    ...
> 	};
> 
> And the defs.h file now looks like:
> 	extern char *strings[];
> 	#define NEWS_VERSION	strings[0]
> 	#define DFLTSUB		strings[1]
> 	...

I suggest instead a defs.h with entries such as:

	extern char *NEWS_VERSION;

and a strings.c file with

	char *NEWS_VERSION = "B 2.10.2 9/5/84";

Advantages:
1. The order in which strings appear is not important.
2. The name of the string is meaningful to symbolic debuggers.
3. 'lint' can tell if strings are being defined but not used.
4. Only one copy of each string.
5. Changing a string doesn't force recompilation of every file
   which includes defs.h.

(the last two advantages are shared by Jerry's model).

Disadvantages:
?? If there were any, I would be the first to admit it :-)

Gary Samuelson
bunker!garys