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 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:
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.)
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.
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.
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.)
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.
If scanf fails, the variable guess is referenced before it has been initialized; accessing an uninitialized object introduces undefined behavior.
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".
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.
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".
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.
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*.
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.
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.
#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.)
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:
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.)
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)
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.
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().)
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