C the complete nonsense

J

James Harris

...


Mostly, though... This was *typical*.  The whole book is like this.
Okay, flipped to a random pgae.
        #include <stdio.h>
        void main(void)
        {
          char str[80];
          freopen("OUTPUT", "w", stdout);
          printf("Enter a string: ");
          gets(str);
          printf(str);
        }
Let's see.
1.  main declared incorrectly.
2.  No error check for freopen.
3.  No newline or flush for the prompt, so no guarantee that
it's actually been written.
4.  Who cares whether it's flushed?  If the freopen() succeeded,
"stdout" will now be the file OUTPUT, so the user won't see the
prompt anyway.
5.  gets() is unsafe and should never be used on buffers, let alone
tiny buffers.
6.  printf(str) can quite easily explode spectacularly if "str"
happens to have any format characters in it.

Your web page is useful. Thanks for writing it. If Nilges causes you
genuine trouble at your publisher's - in other words they choose to
give him any credibility whatsoever - I'd be glad to send them some
comments to redress the balance.

That said, they should see him for what he is: a total kook with
personality problems.

By the way, it would be helpful if the page said at the top what
edition of the book it referred to. That doesn't diminish the
fundamental message to be wary of even well-known writers but it would
make it easier to cross check.

James
 
I

Ian Collins

Those people *need* to destroy to survive. The cult of pedantic
"correctness" over semantic understanding (you can't expect explaining
everything in a tutorial in the first page) and the desire to destroy
all work by people that doesn't belong to their group are their
principal characteristics.

Try explaining that to a compiler. Programming is as much about
correctness as it is about clarity.

Where you get that weird notion about the desire to "destroy" from is
anybody's guess.
 
B

Ben Pfaff

Keith Thompson said:
It's listed in C99 6.5.2 "Postfix operators".

That's convenient, because it fits in the grammar at the same
level of precedence as the postfix operators.
The suffix of a "." operator isn't what I'd call an "operand".
The prefix is an expression. The suffix is not, and cannot be,
an expression.

One form of "sizeof" does not take an expression as its operand.
Do you consider that form of "sizeof" a nullary operator? I have
not heard it described that way before.

[...]
The overriding consideration, IMHO, is that the C standard calls
it a postfix operator.

The C standard lists the "<" operator in a section titled
"Relational operators". Do you consider "<" to be a binary
operator?
 
S

Seebs

Mostly, though... This was *typical*.  The whole book is like this.

Okay, flipped to a random pgae.

        #include <stdio.h>

        void main(void)
        {
          char str[80];
          freopen("OUTPUT", "w", stdout);
          printf("Enter a string: ");
          gets(str);
          printf(str);
        }

Let's see.

1.  main declared incorrectly.
2.  No error check for freopen.
3.  No newline or flush for the prompt, so no guarantee that
it's actually been written.
4.  Who cares whether it's flushed?  If the freopen() succeeded,
"stdout" will now be the file OUTPUT, so the user won't see the
prompt anyway.
5.  gets() is unsafe and should never be used on buffers, let alone
tiny buffers.
6.  printf(str) can quite easily explode spectacularly if "str"
happens to have any format characters in it.

Thanks for quoting this, I missed one.

gets() strips a trailing newline, printf() does not add it, so the
material written to the output file may not be usable; as the default
mode in C is "text mode", the above opened stdout in text mode, and the
behavior of writing text with no line terminator to text mode is not
particularly well-defined.

(That said, I think the fourth edition does change this program; it's
now "int main" and ends with "return 0". None of the other issues are
addressed.)
Your web page is useful. Thanks for writing it. If Nilges causes you
genuine trouble at your publisher's - in other words they choose to
give him any credibility whatsoever - I'd be glad to send them some
comments to redress the balance.

I appreciate the offer. I don't imagine it'll come up, but you never know.

-s
 
K

Keith Thompson

Ben Pfaff said:
That's convenient, because it fits in the grammar at the same
level of precedence as the postfix operators.

Are you suggesting that it's listed there *only* for convenience,
much as "typedef" is treated as a storage-class specifier?
Interesting, I hadn't thought of that.
One form of "sizeof" does not take an expression as its operand.
Do you consider that form of "sizeof" a nullary operator? I have
not heard it described that way before.

Hmm. I probably would think of it as a nullary operator if the
language didn't call it a unary operator.
[...]
The overriding consideration, IMHO, is that the C standard calls
it a postfix operator.

The C standard lists the "<" operator in a section titled
"Relational operators". Do you consider "<" to be a binary
operator?

Certainly.

It has a section called "Unary operators", so we can reasonably assume
that the unary operators are exactly those listed in that section.
(All the unary operators have the same precedence.) It doesn't have a
section called "Binary operators"; instead, the binary operators are
scattered across several sections. It also doesn't have a definition
of "binary operator", so we have to depend on common sense.

I'm a bit uncomfortable with "." being both a postfix operator and a
binary operator, but I can live with it.
 
B

Ben Pfaff

Keith Thompson said:
I'm going to backtrack a bit. I hadn't realize that the C99 standard
has a definition for the word "operand". C99 6.4.6p2:

An _operand_ is an entity on which an operator acts.

I didn't know that either. Thanks for the information.
 
B

bartc

Ben Pfaff said:
REH said:
Next error - "This shorthand [presumably +=, -=,*-, /=, %=, &= etc]
works for all the binary operators". Technically the structure member
operator is a binary operator, as are logical &&. So Schildt's
sentence is literally incorrect.

Actual, the structure member operator is a postfix operator.

The structure member operator has two operands. That makes it a
binary operator.

It's a strange one. The right-hand operand can't be any conventional
expression. I wouldn't call it a proper binary operator.

And (having a quick look in the C99 reference) it does seem to be a post-fix
op followed by an identifier.
 
B

Ben Pfaff

Keith Thompson said:
It has a section called "Unary operators", so we can reasonably assume
that the unary operators are exactly those listed in that section.
(All the unary operators have the same precedence.)

Postfix ++ and -- each have one operand, yet they are not in the
"unary operators" section. (Maybe you consider that not an
exception because prefix ++ and -- are in the "unary operators"
section?)
I'm a bit uncomfortable with "." being both a postfix operator and a
binary operator, but I can live with it.

"Postfix" and "binary" are along two different orthogonal
classification axes. As far as I can tell, an operator can be
postfix, prefix, or infix independently of being unary, binary,
ternary, or N-ary. (I'm not sure that an unary operator can be
infix, unless parentheses for grouping is an example of an infix
unary operator.)
 
S

Seebs

I'm a bit uncomfortable with "." being both a postfix operator and a
binary operator, but I can live with it.

I'm not even entirely sure "operator" is the right name for it.

[] is a postfix-formed binary operator; it needs two things both of which
are values on which to operate. I'm not at all convinced that the structure
member things are exactly "binary operators" in the same sense.

-s
 
R

REH

The structure member operator has two operands.  That makes it a
binary operator.

Not according to the standard. It is classified as a postfix operator.
All binary operators using the E op E syntax, where E is an
expression. The member operator (also the indirection operator) can
only take an expression as the left-hand operator, like all postfix
operators. Its right-hand operand must be a member name.

REH
 
K

Keith Thompson

REH said:
Not according to the standard. It is classified as a postfix operator.
All binary operators using the E op E syntax, where E is an
expression. The member operator (also the indirection operator) can
only take an expression as the left-hand operator, like all postfix
operators. Its right-hand operand must be a member name.

Please cite the section of the standard that says the right operand of
a "binary operator" must be an expression, or that an operator cannot
be both postfix and binary.
 
R

REH

Please cite the section of the standard that says the right operand of
a "binary operator" must be an expression, or that an operator cannot
be both postfix and binary.

Show me an operator listed in the standard as a binary operator that
doesn't take expressions as both the left and right hand operator, or
one that is listed in both sections.

Mathematically, I would agree that the member operator is a binary
operator. But in C is a postfix operator, and the distinction is
relevant when writing a parser. If you've ever written a C parser, the
member operator parser perfectly as a postfix operator (with one extra
lexeme to the right of it), than some special form of binary
operator.

REH
 
S

Seebs

Please cite the section of the standard that says the right operand of
a "binary operator" must be an expression, or that an operator cannot
be both postfix and binary.

Interestingly, the only time I see "binary operator" is by contrast to a
unary operator; thus, "binary +", "binary -", etcetera.

I think I'm going to concede this, based on:

6.5.2.3 Structure and union members

Constraints

1 The first operand of the . operator shall have a qualified or
unqualified structure or union type, and the second operand shall
name a member of that type.

If the second thing, which "shall name a member of that type", is an
"operand", then "." is a binary operator.

I think the use of "operand" is sufficient to make this unambiguous. My
instinctive dislike of calling it a "binary operator" is not enough to
win against the clear text of the standard.

-s
 
R

REH

I think the use of "operand" is sufficient to make this unambiguous.  My
instinctive dislike of calling it a "binary operator" is not enough to
win against the clear text of the standard.

Really? I think the standard is quite clear that it is not (in the
sense of the language) a binary operator, unless someone on the
committee wants to chime in and tell me I am interpreting it wrong.


REH
 
B

Ben Bacarisse

Malcolm McLean said:
Next error: Schild writes printf("%f", sizeof(int)) and printf("%d",
sizeof(int));
The first is indisputably a real error. Without a copy of the book we
can't tell whether it is glitch - the cast somehow missed - or a more
serious error. The second is an error because of the vandalism wreaked
on the C language by size_t. The proposed fix printf("%lu", (unsigned
long) sizeof(int)) would be very confusing to beginners. However we do
have the first real errata entry.

This same error occurs 6 times. At no point does the code from the
book show the beginner how to print a size correctly. The problem
isn't the one error as listed on Seebs's page, but that the book never
seems to tell the reader what they need to know. Sure, gloss over it
early on (though I'd not gloss over it like this) but at some stage
the correct way to print a size_t should be in a "complete reference"
to C.

(Somewhere in the 4the edition it must do so because the author will
have to describe the new printf format letters. No examples appear in
collected code, though.)
Next error - sizeof() an array that is passed as a parameter. A real
error. This is a confusing and inconsistent backwater of C syntax,
however it is a real error and it should be picked up.

If the book is a reference (as per the title) then "inconsistent
backwaters" have to be included. If it is a tutorial for beginners
(as you claim) why is this experienced writer including examples that
use "inconsistent backwaters" of the language? It is either an error
of fact or an error of style.
Next error - "This shorthand [presumably +=, -=,*-, /=, %=, &= etc]
works for all the binary operators". Technically the structure member
operator is a binary operator, as are logical &&. So Schildt's
sentence is literally incorrect. However we don't use natural language
in that very qualified, literal sense. Not a real error.

What, I wonder, is a "real" error -- do you mean that you don't
consider it significant? I don't think it is all that significant
either except that it so easy to avoid. "This shorthand works for
many of the binary operators" or "this shorthand works for all the
arithmetic and bitwise binary operators" would both work, or the
author could simply state the combinations that are permitted.

There are more binary operators than more critics have mentioned.
This statement calls into question the meaning of >= and <= (which now
appear to be assignments) and is posits the existence of ===, !==, >==
and <== which don't exist.
Next error: a variable referenced when scanf() fails. I don't have a
copy of the book. However whilst the result is undefined behaviour, in
practice what will always happen is that a calculations is made with a
garbage value. A real error, but not a serious one.

Except that, in what you call a tutorial, it is repeated time and time
again. I don't think there is a single example that tests the return
form scanf, fscanf or sscanf in the whole book. What a shame that
this tutorial never suggests the useful idiom:

if (scanf("what I want", &where, ...) == number_of_items) {
/* do stuff with my data... */
}
Next error: Schildt uses the terms "stack" and "heap". To be ultra-
correct, of course, we should talk of automatic and dynamic memory, or
whatever the C term is for the malloc() pool, I've forgotten myself.
But this is a classic case of demanding definitions where the author
intends to give an explanation.

So OK, there are a few real errors in Schildt's book which the webpage
picks up. But the rate of real errors to errors created by an
insistence on over-literal interpretations, and the rejection of any
type of simplification for the purposes of readability, isn't very
high. Only one error (the sizeof a parameter) so far is both
unambigiously real and presented in a manner that is fair to Schildt,
and the scanf() error is also real. We're up to page 131 and we've
found one glitchy page where everything seems to be going wrong with
sizeof(), and one minor bug in the use of scanf(). That doesn't seem
to me a particuarly bad error rate for a programming book.

I've commented on you comments just because I take a different view.
I fact I think this to-and-fro over the points in one commentary is
futile and not, ultimately, important.

Focusing on Seeb's web page misses the point. I, too, have not read
the book, but the code is on-line and the examples don't show C being
used well and wisely. This is a great shame in a popular book and is
(to my mind) far more significant than the details pointed out in the
error list. One can argue endlessly if such and such a detail is a
"real error" or not, but i don't think anyone who knows C can argue
the book presents C as it should be written.

There are 92 examples of using gets and quite a few examples of
unconstrained %s input formats. There are no examples of using the
return from scanf (and friends). And then there are some of the most
convoluted input loops I've seem since I stopped marking first year
students' work[1]. printf(s) (or the equivalent) occurs often. Not
every example is dangerous of course, but some are. Why get this
anti-pattern into your reader's heads when there is not need to?

In an error list, you can claim that reading into a char and then
testing it against EOF is a detail, but when that incorrect idiom is
repeated several times the reader will surely come to think that it is
correct and "how things should be done". Learners need to be told why
you read into a int, and not doing so at any point[2] is not a
simplification for the purposes of a smooth presentation of the
material -- it is a disservice to your readers.

If the author keeps passing one (or two) less than the size of the
array to fgets, readers will assume that you /need/ to do this. If
the text explains that you don't have to do this, then readers will be
puzzled by the examples that do. The best (simple) idiom for using
fgets with an array is

if (fgets(buf, sizeof buf, fp)) /* ... use it */

but that is never illustrated. Magic number sizes are used instead
and often odd ones to boot.

size_t values and objects are repeatedly treated as if they were ints.
You may disapprove of size_t, but someone learning C needs to know
what it is and how to use it correctly. Using strlen(s)-1 without
testing for empty strings and printing size_t values using %d (ten
times at least) just gives the confused idea that string lengths and
object sizes are ints when they are not. Learners deserve good,
clear, correct examples of how use these types, functions and
operators correctly.

Please, read the code (nearly 10,000 lines of it) and then report
back. I would be very surprised if you think that it presents
beginners in C with good, clear, correct examples of how to use the
language? That should be the real issue -- not whether this or that
error is a real error, a trivial error, or a serious error.

[1] For example:

char ch;
/* snip */
while(!feof(in)) {
ch = getc(in);
if(!feof(in)) putc(ch, out);
}

or:

while(!feof(fp)) {
fgets(str, 79, fp);
printf(str);
}

or:

char c;
/* snip */
do {
ch = getc(in);
/* snip */
if(ch=='\t') {
/* snip */
}
else {
putc(ch, out);
/* snip */
}
} while(!feof(in));

or:

while(*str) if(!ferror(fp)) fputc(*str++, fp);

or:

while(!feof(fp)) putchar(getc(fp));

[2] It may be explained in the test but if it is, why is it not there
in the code examples?
 
S

Seebs

Really? I think the standard is quite clear that it is not (in the
sense of the language) a binary operator, unless someone on the
committee wants to chime in and tell me I am interpreting it wrong.

The term "binary operator" is never formally defined. As such, I'm
going to fall back on "a binary operator is an operator with two operands".

Since the name-of-a-member is the second of two "operands", the thing it is
an operand of is a binary operator.

-s
 
S

Seebs

And then there are some of the most
convoluted input loops I've seem since I stopped marking first year
students' work[1].
[1] For example:
char ch;
/* snip */
while(!feof(in)) {
ch = getc(in);
if(!feof(in)) putc(ch, out);
}

This one is, strictly speaking, at least correct. Mostly.

But why not write:

int ch;
while ((ch = getc(in)) != EOF)
putc(ch, out);
while(!feof(fp)) {
fgets(str, 79, fp);
printf(str);
}

This one, I'd point out, is essentially GUARANTEED to print the last
line twice. There is no way this code was ever tested.
char c;
/* snip */
do {
ch = getc(in);
/* snip */
if(ch=='\t') {
/* snip */
}
else {
putc(ch, out);
/* snip */
}
} while(!feof(in));

This one will, in general, at least once putc(EOF).

(I'm assuming the "char c" and "putc(ch, ...)" is a typo from your post,
Ben? Or is that an even MORE obvious proof that this stuff never got
compiled?)
while(*str) if(!ferror(fp)) fputc(*str++, fp);

The wrongness of this one is going to be non-obvious to many readers.

As long as you never have a write error, of course, this will work.

Think about what happens if you have a write error, though: When will
the loop terminate? You're never going to increment "str", because the
increment is inside a test against ferror(), which will just keep
returning true.

This is an exceptionally bad illustration of testing an error.
while(!feof(fp)) putchar(getc(fp));

Same deal as the other one; will putchar(EOF) at least once.

And this seems to be *consistent* -- all of the code is crap.

-s
 
B

Ben Bacarisse

Seebs said:
And then there are some of the most
convoluted input loops I've seem since I stopped marking first year
students' work[1].
[1] For example:
char ch;
/* snip */
while(!feof(in)) {
ch = getc(in);
if(!feof(in)) putc(ch, out);
}

This one is, strictly speaking, at least correct. Mostly.

But why not write:

int ch;
while ((ch = getc(in)) != EOF)
putc(ch, out);

Yes, there was no claim that this is wrong, just convoluted. It is a
shame that the book does not present a clear and simple pattern for
using the various input primitives correctly.

I worried that by citing more code my point would get diluted. I
don't mind an error or two. I don't mind the odd idiosyncratic usage.
What I mind is the absence of good advice on (and examples of) how to
use the various input functions correctly:

That you read characters into an int (or a wint_t if you are using
wide characters) and test for EOF (or WEOF); that you test the result
of fgets in the loop; that you test the result of scanf (and friends)
for success rather than failure, and so on. C was designed to be used
in a particular way and to fight against that gives rise to obtuse and
(in some cases) erroneous code.

And this seems to be *consistent* -- all of the code is crap.

I'd rather not say that. In all seriousness, I don't know what the
criteria are for "crap" code -- it means different things to different
people. Going simply by the code examples, the reader will not learn
how to use C simply and correctly and that means that I could not
recommend the book at anyone learning C. That, ultimately, is what
matters.
 
S

Seebs

Seebs said:
And then there are some of the most
convoluted input loops I've seem since I stopped marking first year
students' work[1].
/* snip */
while(!feof(in)) {
ch = getc(in);
if(!feof(in)) putc(ch, out);
}
This one is, strictly speaking, at least correct. Mostly.
Yes, there was no claim that this is wrong, just convoluted. It is a
shame that the book does not present a clear and simple pattern for
using the various input primitives correctly.

Right. I was pointing it out, not because I thought you thought otherwise,
but because it was unusual in that it didn't actually do anything
obviously incorrect, it was just clumsy.
I worried that by citing more code my point would get diluted. I
don't mind an error or two. I don't mind the odd idiosyncratic usage.

What I mind is that:
1. Every usage is idiosyncratic.
2. More examples seem to be buggy than not, at least of those you
found.
What I mind is the absence of good advice on (and examples of) how to
use the various input functions correctly:
Agreed.
I'd rather not say that. In all seriousness, I don't know what the
criteria are for "crap" code -- it means different things to different
people. Going simply by the code examples, the reader will not learn
how to use C simply and correctly and that means that I could not
recommend the book at anyone learning C. That, ultimately, is what
matters.

What I mean by "crap" is that a large number of the examples are
sufficiently poorly-written that I would not expect them to pass any
halfway-decent code review. They are bad examples, they don't
illustrate the points they're supposed to, they don't illustrate how
to do these things expressively or cleanly... It's not useful as
teaching material.

-s
 
B

bartc

Seebs said:
Interestingly, the only time I see "binary operator" is by contrast to a
unary operator; thus, "binary +", "binary -", etcetera.

I think I'm going to concede this, based on:

6.5.2.3 Structure and union members

Constraints

1 The first operand of the . operator shall have a qualified or
unqualified structure or union type, and the second operand shall
name a member of that type.

If the second thing, which "shall name a member of that type", is an
"operand", then "." is a binary operator.

I think the use of "operand" is sufficient to make this unambiguous. My
instinctive dislike of calling it a "binary operator" is not enough to
win against the clear text of the standard.

The standard is being sloppy. The "." symbol is just a bit of syntax that
superficially resembles a proper operator.

But if you think that the "." symbol is an operator, because the standard
says it is, then that's fine...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top