[comp.lang.c] Is this bad programming?

andrew@resam.dk (Leif Andrew Rump) (08/08/90)

Well I know it doesn't look smashing but instead of writing:

#define	FILE_FOOTER_ERROR		1
#define	DRAW_FOOTER_ERROR		2
#define	GRAP_FOOTER_ERROR		3
#define	BASE_FRAME_CREATE_ERROR		4

char	errortext[][40] = {
/*00*/	"234567890123456789012345678901234567890",
/*01*/	"File footer error",
/*02*/	"Draw footer error",
/*03*/	"Grap footer error",
/*04*/	"Base_frame creation error",
	}

which I may write wrong because I forget a line or add a line.
This is allowed by my compiler (I figure the preprocessor
removes it and performs symbol substitution before giving it
to the compiler) and I know that all the lines are there!

char	errortext[][40] = {
	"234567890123456789012345678901234567890",
#define	FILE_FOOTER_ERROR		1
	"File footer error",
#define	DRAW_FOOTER_ERROR		2
	"Draw footer error",
#define	GRAP_FOOTER_ERROR		3
	"Grap footer error",
#define	BASE_FRAME_CREATE_ERROR		4
	"Base_frame creation error",
	}

I am a pascal-programmer (aha, that is why  :-)  ) in my
real life and I could also use union/enumerated types.

Leif Andrew


Leif Andrew Rump, AmbraSoft A/S, Stroedamvej 50, DK-2100 Copenhagen OE, Denmark
UUCP: andrew@ambra.dk, phone: +45 39 27 11 77                /
Currently at Scandinavian Airline Systems                =======/
UUCP: andrew@resam.dk, phone: +45 32 32 22 79                \
SAS, RESAM Project Office, CPHML-V, P.O.BOX 150, DK-2770 Kastrup, Denmark

> > Read oe as: o <backspace> / (slash) and OE as O <backspace> / (slash) < <

karl@haddock.ima.isc.com (Karl Heuer) (08/09/90)

In article <1990Aug8.100614.1223@resam.dk> andrew@resam.dk (Leif Andrew Rump) writes:
>[To avoid having #define indices get out of sync with constant arrays,]
>	char errortext[][40] = {
>	    "234567890123456789012345678901234567890",
>	#define FILE_FOOTER_ERROR  1
>	    "File footer error",
>	#define DRAW_FOOTER_ERROR  2
>	    "Draw footer error",
>	}

Yeah, it works; I don't recommend it.  It doesn't work as well if you have
*two* arrays that use the same enumerated indices, for example; or if you want
to put the indices and the strings in separate files.  I would rather keep the
definitions separate, and add a comment like `/* must be kept in sync! */'.

I recently proposed an extension to allow labeled initializers:
	#define FILE_FOOTER_ERROR  1
	#define DRAW_FOOTER_ERROR  2
	char errortext[3][40] = {
	    0:                 "234567890123456789012345678901234567890",
	    DRAW_FOOTER_ERROR: "Draw footer error",
	    FILE_FOOTER_ERROR: "File footer error",
	};
This might make it into gcc (and maybe from there into C-2001?).

>I am a pascal-programmer

I might have guessed.  A C programmer would likely have used `char *[]'
(instead of padding all the strings to the same length like you have to do in
non-extended Pascal) and made the indices start at zero.

Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint

scs@adam.mit.edu (Steve Summit) (08/09/90)

In article <1990Aug8.100614.1223@resam.dk> andrew@resam.dk (Leif Andrew Rump)
worries about keeping error numbers in sync with an array of
error message strings so that the array can be indexed by the
array number.

Whenever I'm serious about making the connection between error
numbers and messages explicit rather than implicit, I do so
quite, er, explicitly:

	#define	FILE_FOOTER_ERROR		1
	#define	DRAW_FOOTER_ERROR		2
	#define	GRAP_FOOTER_ERROR		3
	#define	BASE_FRAME_CREATE_ERROR		4

	struct errmess
		{
		int em_number;
		char *em_text;
		} errmesses[] =
		{
		FILE_FOOTER_ERROR,	"File footer error",
		DRAW_FOOTER_ERROR,	"Draw footer error",
		GRAP_FOOTER_ERROR,	"Grap footer error",
		BASE_FRAME_CREATE_ERROR,"Base_frame creation error",
		};

This essentially just removes the comment delimiters placed
around the implicit subscripts that are usually provided to
remind the programmer what is going on (i.e.

	/*01*/	"File footer error",

in Leif's example).

This setup still has the disadvantages that the two components of
each line of the structure array definition are quite repetetitive
(FILE_FOOTER_ERROR vs. "file footer error"), and every new error
number must be added in two places (another #define and another
row in the table).  A special-purpose preprocessor (perhaps a sed
or awk script) could easily automate the table building, if
appropriate.

Of course, once the array of structures is set up, it's a simple
matter to write a little routine char *errtext(int errnum) which
takes an error number and searches the array, returning
(similarly to the standard library routine strerror) the
corresponding error string, or "Error %d", (with %d filled in, of
course) for the eventual undefined or illegal error.

The scheme has a few other advantages: error numbers can start at
0, 1, or any other number, they can be sparse or negative, and
the existence (and mandated use; nobody can cheat and index on an
array) of the errtext() routine means there's a convenient hook
(namely, the errtext routine itself) for changing things later to
pull error messages out of a file at run time or some other
useful game.

Someone will howl that a linear scan of the error number/string
pairing array would be "too inefficient," but I would point out
that (a) it wouldn't, if there are a reasonable number of error
numbers, and (b) error message printing is not usually a
bottleneck, and (c) you could rewrite the routine to use binary
search (or, God help you, a hash table) if you really wanted to.
(That's another nice thing about a functional interface: you can
always drop in some other implementation, that needs to gain
control to do something, that you wouldn't be able to do if the
"interface" were simply the access by the "caller" of a global
variable or array.)

                                            Steve Summit
                                            scs@adam.mit.edu

diamond@tkou02.enet.dec.com (diamond@tkovoa) (08/09/90)

In article <1990Aug8.100614.1223@resam.dk> andrew@resam.dk (Leif Andrew Rump) writes:

>char	errortext[][40] = {
>	"234567890123456789012345678901234567890",
>#define	FILE_FOOTER_ERROR		1
>	"File footer error",
>#define	DRAW_FOOTER_ERROR		2
>	"Draw footer error",
>#define	GRAP_FOOTER_ERROR		3
>	"Grap footer error",
>#define	BASE_FRAME_CREATE_ERROR		4
>	"Base_frame creation error",
>	}

Yes, it is perfectly legal, but as you suggested (and I deleted) it
might not be considered aesthetic.

>I am a pascal-programmer (aha, that is why  :-)  ) in my
>real life and I could also use union/enumerated types.

If you want enumeration types, C almost learned them about 11 years ago.
Under ANSI, they've been almost learned in a standardized way.  However,
you might have problems with pre-ANSI compilers (where a single compiler
would often be inconsistent with itself).

typedef enum error_t {
	Filler_to_match_Digit_String,  /* I would delete this and the string */
	FILE_FOOTER_ERROR,
	DRAW_FOOTER_ERROR,
	GRAP_FOOTER_ERROR,
	BASE_FRAME_CREATE_ERROR,
	Number_of_Error_Codes          /* Put new insertions BEFORE this!    */
	} error_t;        /* If you use Posix, then you can't end it with _t */
char	errortext[][40] = {
	"234567890123456789012345678901234567890",  /* I'd make it a comment */
	"File footer error",
	"Draw footer error",
	"Grap footer error",
	"Base_frame creation error",
	}
int	junk = 1 /
	(sizeof errortext[0] * Number_of_Error_Codes == sizeof errortext);
	/* A compile-time error here means mismatched quantities */

-- 
Norman Diamond, Nihon DEC     diamond@tkou02.enet.dec.com
This is me speaking.  If you want to hear the company speak, you need DECtalk.

ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (08/10/90)

In article <1910@tkou02.enet.dec.com>, diamond@tkou02.enet.dec.com (diamond@tkovoa) writes:
> In article <1990Aug8.100614.1223@resam.dk> andrew@resam.dk (Leif Andrew Rump) writes:
> >char	errortext[][40] = {
> >	"234567890123456789012345678901234567890",
> >#define	FILE_FOOTER_ERROR		1
> >	"File footer error",

Instead of all this, suppose you have k separate arrays, k >= 1,
that you want to initialise "in parallel".  You want to give names
to the numbers, and a name to the total number.

The obvious method is to construct a miniature language a la Bentley.
Take a data file like this:

#File: mktable.demo
#
EntryCt;	int whence;	int whither;	char *name
Butcher;	1;		2;		"Butcher"
Baker;		1;		3;		"Baker"
Bellman;	2;		3;		"Bellman"
Boojum;		0;		0;		NULL
#End: mktable.demo

Here the first non-comment line has the form
	<total>; <type 1> <name 1>; ... <type k> <name k>
where <total> is the name that will be #defined to the number of
elements in each array, <name i> is the name of the ith array to
be initialised, and <type i> is the type of its elements.
Subsequent non-comment lines have the form

	<record name>; <value 1>; ... <value k>

where <record name> is the name that will be #defined to the 
record number, and <value i> is a C expression which is to be
one of the initial values of array <name i>.

We feed this through the following AWK script:

#File: mktable.awk
#
BEGIN		{		# note that no header nor data seen yet
		    k = 0	# number of fields required in each record
		    r = 0	# number of data records processed so far
		    p = 0	# number of data *values* saved in data[]
		}
$0 ~ /^[ \t]*#/	{		# ignore comment lines
		    next
		}
k == 0		{		# first non-comment line is header
		    if (NF < 2) {
			print "Error in line " NR ": 2 or more names expected"
			exit
		    }
		    k = NF	# number of fields to be in EACH line
		    for (i = 1; i <= NF; i++) name[i] = $i
		    next	# don't treat this as data!
		}
k != 0		{		# this must be a data record
		    if (NF != k) {
			print "Error in line " NR ": wrong number of fields"
			exit
		    }
		    print "#define", $1, r	# define the record number
		    r++				# then save the data away
		    for (i = 2; i <= NF; i++) data[p++] = $i
		}
END		{				# all records seen
		    if (k == 0) {		# never saw a header
			print "Error; no header record present"
			exit
		    }
		    if (r == 0) {		# never saw any data
			print "Error; no data records present"
			exit
		    }
		    # otherwise, data[] is r blocks of (k-1) strings
		    print "#define", name[1], r
		    for (i = 2; i <= k; i++) {
			print name[i] "[] = {"
			for (t = i-2; t < p; t += k-1) print "\t" data[t] ","
			print "};"
		    }
		}
#End: mktable.awk

In UNIX it is easy to bolt all this together

	awk -F\;              -f mktable.awk  mktable.demo | cb >tables.c
	    ^field separator  ^awk program    ^data file         ^result

(Note that using ; as the field separator allows spaces in expressions.
If you want "strings" or 'ints' containing ";", use some other character.)

The result is
#define Butcher 0
#define Baker 1
#define Bellman 2
#define Boojum 3
#define EntryCt 4
int whence[] = {
	1,
	1,
	2,
	0,
};
int whither[] = {
	2,
	3,
	3,
	0,
};
char *name[] = {
	"Butcher",
	"Baker",
	"Bellman",
	NULL,
};

In MS-DOS, it's a little harder (you might have to get the MKS awk,
for example).  On the other hand, it would be pretty easy to translate
this awk program to C.  In any case, just #include the result into your
C program, and Bob's your uncle!

-- 
Taphonomy begins at death.

ronald@robobar.co.uk (Ronald S H Khoo) (08/11/90)

In article <3541@goanna.cs.rmit.oz.au> ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) writes:

> In MS-DOS, it's a little harder (you might have to get the MKS awk,
> for example).

GAWK was posted to comp.binaries.ibm.pc.  No problem.

-- 
Eunet: Ronald.Khoo@robobar.Co.Uk  Phone: +44 81 991 1142  Fax: +44 81 998 8343
Paper: Robobar Ltd. 22 Wadsworth Road, Perivale, Middx., UB6 7JD ENGLAND.

friedl@mtndew.Tustin.CA.US (Steve Friedl) (08/20/90)

Steve Summit writes:
> [Others] worry about keeping error numbers in sync with an array of
> error message strings so that the array can be indexed by the
> array number.
> 
> Whenever I'm serious about making the connection between error
> numbers and messages explicit rather than implicit, I do so
> quite, er, explicitly:
> 
> 	#define	FILE_FOOTER_ERROR		1
> 	#define	DRAW_FOOTER_ERROR		2
> 
> 	struct errmess { int em_number; char *em_text; } errmesses[] =
>	{ FILE_FOOTER_ERROR,	"File footer error",
>	  DRAW_FOOTER_ERROR,	"Draw footer error",
>	  ... };

[ style compressed by me ]

I occasionally have to do this kind of thing for a case where I must
access the array repeatedly, and the search time is more than I care
to allow.  So, I define a table just as Steve does above, then define
an auxilliary table of pointers to these structs that I build at
runtime.  This way I can lay out the table in no particular order,
and the runtime sorter stuffs in all the relevant values (including
a pointer to a single static "dummy" value for holes in the array).

It does require a hit at runtime, but it means that I can do things
like an array of signal names without having to do a *horrible*
#ifdef dance that depends on my knowing which signal numbers have
which values.  I have seen people try to build these tables for
multiple machines, and in my opinion it is ridiculous to try to
do so.

     Steve

-- 
Stephen J. Friedl, KA8CMY / Software Consultant / Tustin, CA / 3B2-kind-of-guy
+1 714 544 6561  / friedl@mtndew.Tustin.CA.US  / {uunet,attmail}!mtndew!friedl

Combat global warming -- leave the refrigerator door open

ramsey@NCoast.ORG (Cedric Ramsey) (08/20/90)

In article <499@mtndew.Tustin.CA.US> friedl@mtndew.Tustin.CA.US (Steve Friedl) writes:
>Steve Summit writes:
>> [Others] worry about keeping error numbers in sync with an array of
>> error message strings so that the array can be indexed by the
>> array number.
>> 
>> Whenever I'm serious about making the connection between error
>> numbers and messages explicit rather than implicit, I do so
>> quite, er, explicitly:
>> 
>> 	#define	FILE_FOOTER_ERROR		1
>> 	#define	DRAW_FOOTER_ERROR		2
>> 
>> 	struct errmess { int em_number; char *em_text; } errmesses[] =
>>	{ FILE_FOOTER_ERROR,	"File footer error",
>>	  DRAW_FOOTER_ERROR,	"Draw footer error",
>>	  ... };

[deleted stuff]

	If you have to ask then it probably is ! ! !