[comp.lang.c] Review: "Portable C", Rabinowitz and Schaap

mcdaniel@amara.uucp (Tim McDaniel) (03/21/90)

Henry Rabinowitz and Chaim Schaap, "Portable C", Prentice-Hall,
Englewood Cliffs, N.J., 1990.  ISBN 0-13-685967-4, $30.00, 269 pages.

Picoreview:  Not quite recommended.

Nanoreview: A generally decent book, with a goodly amount of rigor.
However, there are enough errors, large and small, that I can't quite
recommend buying this book.

(In this review, I emphasize text by putting it in uppercase.  All
such emphasis is my own.)

I consider myself to be a C guru (many thanks to Chris Torek, Doug
Gwyn, Henry Spencer, et al), and I've read a few portability guides
(many thanks to Andrew Koenig's "C Traps and Pitfalls").  I'm sure a
newish C programmer would consider "Portable C" to be a very different
book than I do.  In fact, I have a hard time reviewing this book,
because so many of the ideas were already familiar to me.

This is generally a pretty decent book.  They warn about differences
among various implementations, and differences between older imple-
mentations and ANSI C.  They condense discussions into rules, and list
these rules in Appendix A.  These rules are generally good.  They sink
the term "lvalue", and just talk about "modifiable" and "addressable",
a welcome clarification indeed!  They are careful to point out that a
data object has attributes (type, expression, name, value, access
permissions) and that intermediate values are data objects with
attributes -- again, most welcome!  The authors know quite a bit about C.

However, there are enough errors, large and small, that I can't quite
recommend buying the book.

I think the major problem is the treatment of pointers.  A large block
of formalism for these concepts is developed in Chapter 2, and
apparently not used until Chapters 6 and 7.  Furthermore, the pointer
discussion confused me on first reading, and I think I know what
they're trying to say!

I also believe that they don't quite understand null pointers.  From
time to time (e.g. 7.3.2's section header), they refer to "THE null
pointer".  There's no such animal in C -- there's an unbounded number
of null pointers, one per pointer type declared.  They say "The only
integer VALUE that can be portably assigned to any pointer is 0." (p.
140, paragraph 3).  Integer VALUES cannot be assigned to pointers; the
integer CONSTANT "0", appearing in a context requiring a pointer to a
known type, is converted to a null pointer of that pointer type.
(That constraint is mentioned in only two places, one a footnote
quoting K&R!  Unqualified 0s, as well as 0 in the program text
typeface, abound.)  In 7.3.2, they condemn the false conception that
"*(char *)0" is '\0'.  However, in 7.3.3, they condemn "the assumption
among some VAX programmers that *NULL is '\0'".  NULL has no fixed
type (int, void *, or char *, depending on implementation); "*NULL"
usually expands to "*0" or "*(void *) 0", which is a syntax error.  I
think there are some bits of confusion dealing with conversion, such
as footnote 3 on page 119, about "int b[3][4];":
    Note that the expression 'b[0]' refers to an array of 4 integers.
    That is, 'b[0]' has type 'int [4]'.  However, in the context in
    which it appears in the code fragment above [a value context],
    like most expressions denoting arrays, it acts as a pointer to the
    first element of the array it refers to; thus, it has type 'int *'. 

(They make similar statements about the use of function names.)
Perhaps I'm too picky, but I think it's better to think of a
conversion: 'b[0]', of type 'int [4]', is implicitly converted to an
unnamed constant data object of type 'int *'; the new constant has the
address of the first element of the array.  I think that this helps
clear up subtle issues, such as note 1 in Exercise 6-1.

This is unfortunate, because pointers in C is one of the more subtle
concepts to master, and possible the one concept that causes the most
trouble.

There are also a dozen or so small glitches scattered throughout.  For
example, they give a portable code fragment in section 1.1.3:
	#include <string.h>
	#define VERSION "1.03"	/* keep version number as a macro
				 * to make changes easy */
	void prVersion();
	...
	void
	prVersion()
	{
		static char outmsg[20] = "Version #";
		printf(strcat(outmsg, VERSION));
	}
It does seem portable, but it's not really maintainable.
- VERSION need not be a constant for the program to work correctly; it
  could be declared as a "char *" variable instead. 
  Preprocessor names don't follow the normal scope and naming rules,
  and aren't often visible in a debugger.  In My Humble Opinion, they
  should be avoided wherever possible.
- What happens if the version string becomes longer than 10 characters?
- What happens if someone puts a "%" in VERSION?

Sec. 9.8: ''The following example shows a portable definition for
STRINGIZE...:
        #ifdef __STDC__
        #define STRINGIZE(a)    #a
        #else
        #define STRINGIZE(a)    "a"
        #endif''
If the second definition is so portable, why did ANSI disallow it and
give another mechanism?  Neither stringize nor token concatenation is
portable.  Yes, they also give a "portable" CONCAT_TOKEN macro.  These
definitions are "somewhat more portable".  They are NOT "portable"
without qualification.

In section 3.2, they explicitly suggest/require that function
declarations be prototyped but function definitions be non-prototyped.
However, rule FUNC2 (3.2.2) says "The actual and formal parameters of
a function should have the same types.".  They only describe default
argument promotions in a footnote.  Problems mixing prototypes with
non-prototypes have arisen at least twice in comp.lang.c; a
substantial discussion of the possible pitfalls is needed.

They use "#ifdef __STDC__" to test for ANSI C-ness.  ("#if __STDC__ ==
1" is more likely to work.)  Sec. 4.9: "Floating point types are
excluded from use as Booleans because ... Also, floating point zero
may not be all-bits zero (false) in some environments."  (Irrelevant:
Boolean false does NOT mean all-bits zero; "if (e)" means "if ((e) ==
(typeof(e)) 0)", to borrow a GCCism.)  Sec. 8.2.1: one of the reasons
they suggest using unique member names is to assure "portability to
older compilers".  (Are there really compilers that ancient still
around?  Many of them?  Should we care?)  Sec. 9.9.2: "lint exists in
all UNIX environments".  (Just try to use standard lint on ANSI C
prototypes.)

They credit comp.lang.c and thank "Brian Kernighan [and others] for
their careful reading of our manuscripts".  This statement is rather
surprising.

Section 9.10 is totally inexplicable in light of the rest of the book.

''9.10 Porting other people's code

It is possible to port a large program written by others without
understanding how it works.  At most, you may have to read some small
sections of code.  Here is a step-by-step procedure that has proven
effective in porting other people's code.

1. Look at the makefile(s) for the code and adjust all
environment-specific settings to fit your environment.

2. Look at all the #ifs and #ifdefs in the code to make sure the flags
are defined appropriately for your environment.

3. Try to make the program.  Often, it will fail to compile.  Make the
changes necessary to get it to compile and link.

4. Lint the code.  Lint all the source files together if possible.
Make appropriate changes in the code.

5. Make the program again.

6. Run the program.  If it dies, use a runtime checker for diagnosis,
or as a last resort, use a debugger.  Repair the program, return to
step 3, and repeat.''

"Incredibly naive" was the phrase that came to mind upon reading this
section.  If porting were so mechanical, what would be the point of
the book?

I don't think the book is unsalvagable by any means.  A second edition
could be an excellent book, providing a rigorous view of the C
abstract machine model as well as helpful guidelines in writing code.
However, it's just not quite up to snuff right now.

I recommend Andrew Koenig's book, "C Traps and Pitfalls",
Addison-Wesley Publishing Company, Reading, Massachusetts, 1989.  ISBN
0-201-17928-8.  It's not as ambitious, but it succeeds beautifully.

I am also eagerly awaiting Mark Horton's new book, "Portable C
Software", Prentice Hall, 1990, ISBN 0-13-868050-7, $32.95.

--
Tim McDaniel
Applied Dynamics International, Ann Arbor, MI
Internet: mcdaniel%amara.uucp@mailgw.cc.umich.edu
UUCP: {uunet,sharkey}!amara!mcdaniel