NOTE: This document is obsolescent.

This document was written in 1996, in response to the 3rd edition of Schildt's C: The Complete Reference. Recent events have encouraged me to review the more recent 4th edition (released in the year 2000). Please use C: The Complete Nonsense instead of this page, which is preserved for historical interest.

Please note that this does not constitute a "retraction" of this page. The complaints below are, by and large, accurate complaints about flaws in the 3rd edition of Schildt's book. They continue to strike me as representative of the poor quality of the book. However, I would like to state that, with a lot more experience and knowledge, I now feel that this document did not go nearly far enough in condemning the atrocious writing in C:TCR. The new document attempts to do a better job of accurately capturing the sheer scope of the madness.

C: The Complete Nonsense

C: The Complete Reference is a popular programming book, marred only by the fact that it is largely tripe. Herbert Schildt has a knack for clear, readable text (editorial: actually, he doesn't, he just has a knack for text which seems clear at first glance if you don't read it carefully...), describing a language subtly but quite definitely different from C. This page aims to give people a good way to find out what's wrong with it.

Don't bother contacting the publisher; they apparently don't feel these errors are significant.

The following is a partial list of the errors I am aware of, sorted by page number. I am not including everything; just many of them.

I am missing several hundred errors. Please write me if you think you know of any I'm missing. Please also write if you believe one of these corrections is inadequate or wrong; I'd love to see it.

Currently known:

Page 19
In general, negative numbers are represented using the two's complement approach...

This is not a C feature. It is a common implementation, but it is specifically not required. (Binary is, but one's complement is not unheard of.)

Page 33

The following heading occurs:

static Global Variables

No such thing. A static variable outside of a function has file scope, which is distinct from global scope.

Page 53
	printf("%f", sizeof f);
	printf("%d", sizeof(int));

Clearly wrong; sizeof is not a double or float. It is also not an int; it is an unsigned integral type, thus, one of unsigned char, unsigned short, unsigned int, or unsigned long.

The only safe way to do this is:

	printf("%lu", (unsigned long) sizeof(int));

While this is larger, a clear explanation of why it is required will go a long way towards helping people understand C.

Page 53

The following code:

	/* Write 6 integers to a disk file. */
	void put_rec(int rec[6], FILE *fp)
	{
	  int len;
	
	  len = fwrite(rec, sizeof rec, 1, fp);
	  if (len != 1) printf("write error");
	}

Is described as causing all of rec to be written, no matter what size of array is being used.

Incorrect. As correctly noted elsewhere, when "int rec[6]" is an argument to a function, it actually specifies a pointer-to-int, not an array[6]-of-int. sizeof rec is sizeof(int *) here, and this code works only if sizeof(int *) is precisely 6 times sizeof(int). (Not impossible, but hardly likely.)

Further, who said fp was a disk file? fp could be stdout. (An admitted nit.)

Page 59
This shorthand works for all the binary operators...

No, it doesn't. It doesn't work for ".", "->", "&&", or "||". For that matter, it doesn't work for the function call operator or the array operator, both of which are, roughly, binary operators.

Page 63

If scanf fails, the variable guess is referenced before it has been initialized; accessing an uninitialized object introduces undefined behavior.

Page 131

Memory allocated by C's dynamic allocation functions is obtained from the heap -- the region of free memory that lies between your program and its permanent storage area and the stack.

C does not specify that there is a stack - only that functions can call each other. The "heap" is a DOS term, and the layout is not a part of the C language. It is not atypical for the layout to be radically different, and certainly, there is no call for describing a specific choice as "what happens".

Page 132
After the assignment, p points to the first 1000 bytes of free memory.

No, p points to at least 1000 bytes of allocated space, which is not free memory. There is also no reason to assume it was the "first" 1000 bytes; top-down allocation is not atypical, and further, there's no reason to assume this code fragment runs in isolation.

Page 162

Functions are not of type void; functions are of various types, called collectively the function types. A function may have a return of type void, which means that its type is something like "function taking (...) and returning void".

Page 163
You may also declare main() as void if it does not return a value.

Specifically untrue. ANSI mandates two declarations for main, and says that main may have declarations compatible with those. Both return int.

Page 197

It is redundant to give a size of char in bytes as 1 as an "assumption" - it's the definition, sizeof() gives the size in *chars*.

Page 247

The stream fp is opened with mode "r", the mode to open a text file. Then, fseek is called on fp, with the 2nd argument not a value returned by a previous call to ftell.

ANSI 7.9.9.2

For a text stream, either offset shall be zero, or offset shall be a value returned by an earlier call to the ftell function on the same stream and whence shall be SEEK_SET.

In other words, this is blatantly invalid.

Page 253
In most implementations, the operation fails if the file specified in the open statement does not exist on the disk.

To the best of my knowledge, POSIX (the standard for the open() call) documents and requires the functionality of the O_CREAT flag.

Page 283

	#include <string.h>
	
	char s1[] = "hello ";
	char s2[] = "there.";
	
	void main(void)
	{
	  int p;
	
	  p = strcat(s1, s2);
	}

It is correctly noted that this generates a warning. Not mentioned is that it's invalid; although s1[] is a modifiable array, it is an array large enough to hold "hello " (and the terminating null byte), so it has room for 7 bytes. The strcat overflows the array, producing undefined behavior.

(And, of course, the declaration of main is invalid.)

Page 284
All of the header files are listed in capitals; the standard specifies them in lower case. It is not required that a C compiler reject all-caps, but nor is it required that it accept them.
Page 314
However, since EOF is a valid integer value, you must use feof() to check for end-of-file when working with binary files.

Not merely a little bit untrue, but utterly wrong, and specifically missing the point of the rule (correctly stated) about returning the char as "unsigned char converted to int" (actually stated in the standard in 7.9.7.1, under fgetc()).

Since EOF is a negative integral constant, it can never compare equal to any unsigned char. When you are reading from a binary file, the values you get will never compare equal to EOF, until getchar() returns EOF because the file is empty.

This correlates with a mistake made in all of the examples where loops break on '$', 'A', or ' ' because the return from getchar() is immediately put into a char variable.

This is a more serious flaw than many, because it results in poorly written, inefficient code.

(Couple this with the consistent attempts to use feof() to see if the next read will fail, when in fact feof() only returns true when the previous read failed, and you get a completely wrong description of the standard I/O library.)

Also, several of the programs given loop forever if an end of file is reached, because EOF is not checked for in a loop.

(The astute reader will note that he is correct for implementations in which char and int are the same size; I disregard this because:

  1. This violates the spirit, if not the letter, of the standard.
  2. The implementation he is discussing does not have this problem.

In such an environment, the "correct" thing to do is probably to use fread and check for failure. feof() will still not warn you that your next read will fail.)

Page 333

After

	char str[80];

	sprintf(str,"%s %d %c", "one", 2, 3);

it is asserted that str will contain "one 2 3". This is incorrect; it would contain "one 2 ^C". (That's "control C" in ASCII, or a character with the value 3)

Page 348

Schildt says "This program checks each character read from stdin and reports all uppercase letters:". He is wrong.

	#include <ctype.h>
	#include <stdio.h>

	void main(void)
	{
	  char ch;

	  for (;;) {
	    ch = getchar();
	    if(ch == ' ') break;
	    if(isupper(ch)) printf("%c is uppercase\n", ch);
	  }
	}

The code works only if there are no uppercase letters following the first space in the standard input stream; further, a file consisting only of the word "hello" will prevent this horribly broken code from terminating - because it doesn't check for EOF, only for a space.

Once again, even a slight clue about EOF would help a lot here.

Page 434
free() must only be called with a pointer that was previously allocated with one of the dynamic allocation system's functions (either malloc(), realloc(), or calloc()).

Also specifically untrue. ANSI states that free(NULL) is valid and has no effect. (Also note that it must be called with a pointer to space previously allocated, not with a pointer previously allocated, and that the pointer must not have been already freed or passed to realloc().)

Page 735

This is spectacularly wrong; the "corrected"

x = *p * (*p++);

is EXACTLY equivalent in terms of C; as correctly noted earlier, the order of evaluation IS NOT SPECIFIED.

The code is still invalid (p is used to determine *p on the left of the *, as well as modified on the right), and the parentheses aren't affecting the code at all.

In this code, p can be incremented anywhere in the line; the only requirement would be that the value of (*p++) be the same as the value of (*p) before the increment. It is not specified whether the other *p happens before or after the increment.

In fact, because the code modifies an object (p) and uses the value of the object to do something other than determine the new value (The first "*p"), it is invalid. Completely; a compiler is allowed to reject the code, and many will produce surprising results from this operation.

This is not merely wrong, it's wrong while discussing the problem, which is doubly bad.

There are dozens of others, and I'm sure there's an effective drinking game lurking in this book.


Comments about this web page can be sent to:

seebs@plethora.net