New book - 'C of Peril'

P

Paul L Daniels

Hello All,

I've been compiling a series of pages about the bad functions and
mistakes that occur in C into a book, subsequently, I now have a moderate
little book of about 32 pages available for download as a PDF file at

http://www.pldaniels.com/c-of-peril

It's currently still being worked on and I'm always seeking out new
examples of C being used poorly or broken standard functions.

Regards.
 
R

Rouben Rostamian

Hello All,

I've been compiling a series of pages about the bad functions and
mistakes that occur in C into a book, subsequently, I now have a moderate
little book of about 32 pages available for download as a PDF file at

http://www.pldaniels.com/c-of-peril

It's currently still being worked on and I'm always seeking out new
examples of C being used poorly or broken standard functions.

I looked quickly through your writeup. It's pretty good.
Here are my remarks on a couple of items that sort of jumped at me.

1.
On the section on free() you wrote:

Certainly it is agreeable that if you are attempting to free a
null pointer, then you're most certainly in a situation where
somewhere prior in your code there is something amiss.

Certainly it is agreeable? Not!

I free() NULL pointers frequently and intentionally. Here is a sample:

void somefunction(void)
{
char *x, *y, *z;

x = malloc(100);
y = malloc(100);
z = malloc(100);

if (x==NULL || y==NULL || z==NULL)
goto cleanup;

/* do some real work here */

cleanup:
free(x);
free(y);
free(z);
}

Suppose that the first call to malloc() succeeds but the 2nd
and 3rd calls fail. Then free() is called with non-NULL x
but with NULL values for y and z. Nothing wrong with that.
And certainly it's not true that "there is something amiss".


2.
On the section on macros, you suggest:

#define foo(x) { bar(x); baz(x); }

This is not really good. For example, the following
snippet will not compile:

if (sometest)
foo(x);
else
fubar(x);

You want to change the macro to the canonical form:

#define foo(x) do { bar(x); baz(x); } while(0)
 
T

Trent Buck

Up spake Rouben Rostamian:
#define foo(x) do { bar(x); baz(x); } while(0)

This will still cause problems if the parameter has side effect(s).
Consider:

#define foo(x) do { bar(x); baz(x); } while(0)
foo (printf ("oops!"));

If you know the type of x in advance[0], you can create a local
variable. Consider:

#define foo(x) do { int y = (x); bar (y); baz (y); } while (0)

However, this in turn causes problems with variable capture. Consider:

#include <stdio.h>
#define foo(x) do { int y = (x); bar (y); bar (y); } while (0)

void bar (int y) { printf ("%d ", y); }

int main (void) {
int x, y;

x = y = 4;
foo (x + y);
printf ("%d\n", y);

return 0;
}

Intuitively, one would expect this to print "8 8 4". On my system, it
prints "1091899344 1091899344 4", because the symbol `y' in `foo (x +
y)' refers to the inner declaration's variable, which is noninitialized.

The only workaround I have found to prevent variable capture is to give
the macro variable an obscure name (e.g. `mypackage_mymacro_myvariable')
and hope nobody else uses it.

[0] OT: The GNU typeof extension provides genericity here.
 
P

Paul L Daniels

Rouben,

First up, thanks for the responses, always welcome so you can bet that
there will be some changes made :)
I free() NULL pointers frequently and intentionally. Here is a sample:

Do you run into portability issues relying on this behavior?
x = malloc(100);
y = malloc(100);
z = malloc(100);

Now, perhaps this is going to open up a can of worms, but would it not be
prudent to check the state of x/y/z _before_ proceeding to the next
malloc? I realise that it makes for longer, possibly even messier code,
however is it not more "right" to check after each malloc?
if (x==NULL || y==NULL || z==NULL)
goto cleanup

I can see someone jumping on this one (goto debates are always amusing).

You want to change the macro to the canonical form:

#define foo(x) do { bar(x); baz(x); } while(0)

Anyone know if there's a way to do this without relying on do/while/if/for
(etc)?

Or are we bound to these one-shot do/while loops in order to handle the
trailing ';' ?


Regards.
 
P

Paul L Daniels

I should point out that the 'macros' section is one of the less mature
sections in the book, hence it's good that you guys (girls?) are chewing
on it.
The only workaround I have found to prevent variable capture is to give
the macro variable an obscure name (e.g. `mypackage_mymacro_myvariable')
and hope nobody else uses it.

It would seem to me that for every (sane) macro solution that is derived,
there lies a newly created problem situation where it won't work. This in
itself is precisely why this book is being written, even if you cannot
derive complete solutions, increasing the awareness of the problems is a
desired goal.

Trent, I'd like to consider inserting the sequence of macro
attempts/failures that you have just presented (perhaps modified a litte)
as an 'example' of how macros can go wrong.

Kindly,
Paul.
 
A

Arthur J. O'Dwyer

I've been compiling a series of pages about the bad functions and
mistakes that occur in C into a book, subsequently, I now have a moderate

Incidentally, that introductory paragraph shows about the quality of
English writing we can expect from the whole book --- not so hot. I
have relatively little complaint with the actual technical content of
the document, but the presentation is awful. At the very /least/ run
a spell-checker on the text, and I'd recommend getting a good reader
to look it over for clarity before unleashing it on the unsuspecting
populace, as it were. </rant>

<rant mode=continue> Oh, and for Pete's sake don't use hard tabs on
Usenet! </ok done now really>


2.1: 'strcpy' is definitely not the first function I'd go to in the
"peril" department. After all, it's perfectly safe when used correctly,
and incredibly hard to mess up. Even in your example, you have to resort
to a function in which you declare a length ('10') and then proceed to
forget you ever declared it.
You should find out how to keep figures and code listings on one page
of the generated PDF, and use that feature liberally. It's hard to read
code that jumps across pages unnecessarily.
The second source listing has an extraneous ':' character in it.

2.2: "wil not" is spelled "will not" on my Red Hat system. I don't
know what system you have. (Google does provide circumstantial evidence
for the "wil not" spelling, 104 to 88.)
The correct fix for this 'printf' error is to use the "%*s" format
specifier instead of the "%s" specifier, and pass 'FOO_STR_LEN' to
'printf' yourself.
Returning 'NULL' from 'CP_strncpy' under any circumstances strikes
me as highly dangerous. Do you know why 'strcpy' and family always
return the destination pointer?
What will the following code do, and why? Try to answer first without
looking at the source code of 'CP_strncpy'.
char arr[10];
CP_strncpy(arr, "hello", strlen("hello"));

2.3: C++ programmers will not like that you use a struct with the
same name as a function.

2.4: Why 'CP_strncpy' but 'zstrncat'? Is there a method to your naming
convention?
Several of your single-line comments spill over and become syntax
errors; this is exactly why Real Programmers[tm] do not use C++-style
comments.

2.5: Are you going to explain /how/ to use 'snprintf' to emulate
'strncpy' (or rather, what you think 'strncpy' ought to be doing)?
I bet you'll have at least one reader who wonders why
snprintf(dst, n, src);
does not always work as expected.

3.1: A more realistic example (i.e., one that would do something
reasonable if it worked) would be helpful here. Your current example
merely loops forever --- who cares what it's doing with memory? Fixing
the "bug" won't make the program work!
Your "safe" usage contains at least one instance of undefined behavior,
not counting the obvious one.
You don't discuss a real-life programming error which I have recently
encountered in my own code --- /twice/!

int k, *pids = NULL;
int pids_len=0, pids_cap=0;
while ((k=getpid()) != NULL) {
/* Do we need to resize the |pids| array? */
if (pids_len >= pids_cap) {
void *v;
pids_cap = pids_cap*2 + 15;
v = realloc(pids, pids_cap);
if (v == NULL) do_error("Out of memory");
pids = v;
}
/* Add the new pid */
pids[pids_len++] = k;
}
/* Process array |pids|, and free it when we're done */
free(pids);
return;

3.2: "Redundant code makes for slower and larger programs." Are
you claiming that the statement 'free(p);' is more redundant than
the statement 'if (p != NULL) free(p);'? I don't think you'll find
many takers.
Listing 3.3 is labeled "Example of segmentation faulting using free,"
but it does not exhibit a segfault --- it's perfectly valid. Remove
the '= NULL' initializer and you've got a case.
Your "graceful exit" uses the non-portable expression 'exit(1)'.

3.4: 'sizeof' is not a function.
You are writing about C99 (since you use C++-style comments in earlier
listings). Therefore you should know that 'sizeof' is no longer strictly
a compile-time construct, when you're dealing with VLA expressions.
Your "As a rule" goes utterly against the comp.lang.c majority opinion.
Read the archives for enlightenment on both sides of this debate.

4.1: Do you know about
if ((p = strchr(buffer, '\n'))) *p = '\0';

4.3: Do you know about the * modifier?

4.4: 'syslog' is not a standard C function. You might want to explain
where it's used; I for one have never heard of it, and have no idea what
'syslog(1, ...)' is supposed to do.
The assumption that large compiles must necessarily generate lots of
spurious warnings is a dangerous one. Consider fixing warnings as they
appear.

5.2: Your example code is right. This would normally be a good thing,
but in this case you're supposed to be illustrating /wrong/ code.

5.3: The original example code for 5.2 will do nicely; consider
both the "bottom-up" case 'min(1&2, 1&3)' and the "top-down"
case '1 + min(1,2)'. Ditto for 5.4.

5.5: Note that the given output is only hypothetical; the program
exhibits undefined behavior.

5.6: Others have already pointed out the error and the textbook
solution. You might also consider addressing the pitfall of

#define foo(x) do { \
bar(x); \
baz(x); \
} while (0) \
#define bar(x) do { \
wubble(x); \
} while (0) \

Yes, it's easy to catch and easy to fix, but then so are many of the
problems you address.

6: "programming ... is not the place to express creativity ..."
Obviously you have never programmed!

6.1: However, note that a source file full of /too/ many characters
can be even worse! I'm sure you can find examples in the newsgroup
archives of instances in which our regulars have dissected newbie
programs redolent with excess parentheses and levels of nesting, only
to discover that the underlying algorithm can be expressed in five
lines of concise and idiomatic code. (By which I do /not/ mean Ben
Pfaff's signature file! When /I/ use the word "idiomatic," it is a
compliment!)

I hope these comments help you improve the book's technical content.
And remember what I said about the proofreader.

-Arthur
 
A

Arthur J. O'Dwyer

Rouben,

Do you run into portability issues relying on this behavior?

Why should he? The only implementations that have problems with
freeing null pointers are pre-C89 versions, which are generally not
supported anymore except by the most venerable of programs (Nethack,
for example, still supports K&R C, AFAIK). And in your book, you're
assuming C99 already!
Now, perhaps this is going to open up a can of worms, but would it not be
prudent to check the state of x/y/z _before_ proceeding to the next
malloc? I realise that it makes for longer, possibly even messier code,
however is it not more "right" to check after each malloc?

Didn't you complain in section 3.2 about redundant code?

Anyone know if there's a way to do this without relying on do/while/if/for
(etc)?

Nope. But why does it matter? 'do' is just as much a part of the
language as the left parenthesis, and it always has been.

-Arthur
 
T

Trent Buck

Up spake Paul L Daniels:
Trent, I'd like to consider inserting the sequence of macro
attempts/failures that you have just presented (perhaps modified a litte)
as an 'example' of how macros can go wrong.

I hereby release the macros I wrote upthread to the Public Domain. I
consider everything I post on Usenet to be PD, but it's commendable of
you to ask.
It would seem to me that for every (sane) macro solution that is derived,
there lies a newly created problem situation where it won't work. This in
itself is precisely why this book is being written, even if you cannot
derive complete solutions, increasing the awareness of the problems is a
desired goal.

The Paul Graham has a lot to say about macros and the accompanying
problems. Not in C, but the issues are similar.

http://www.bookshelf.jp/texi/onlisp/onlisp_13.html#SEC68
http://www.bookshelf.jp/texi/onlisp/onlisp_14.html#SEC78
 
P

Paul L Daniels

Arthur,

Ahh, the warm welcome-back of newsgroups.
Incidentally, that introductory paragraph shows about the quality of
English writing we can expect from the whole book --- not so hot.

Never claimed it to yet be an exercise in English literature. The book is
incomplete and will go through an editing phase by 3rd parties.


[...many useful hints and questions...]
I hope these comments help you improve the book's technical content.
And remember what I said about the proofreader.

The book will be ammended, updated as usual, in a never ending cycle (it
would seem). As always, I do appreciate the input. If there's one thing
to be aware of, it is that you can never know everything (your comments
have proven that much - though I didn't need it proven ;-).

Regards.
 
T

Trent Buck

Up spake Paul L Daniels:
Anyone know if there's a way to do this without relying on do/while/if/for
(etc)?

The other technique I sometimes see is !!GNU EXTENSION!! expressions
within statements. Consider this !!NON STANDARD!! assertion wrapper:

#ifdef PROHIBIT_STATEMENT_EXPRS
/* WARNING! assert_lambda() macros will not assert,
* because statement expressions are prohibited.
*/
# define defun_assert_lambda(TYPE,TEST,FUNC) FUNC
#else
# define defun_assert_lambda(TYPE,TEST,FUNC) \
({ \
TYPE aug_assert_lambda_x; \
assert \
(TEST != (aug_assert_lambda_x = FUNC)); \
aug_assert_lambda_x; \
})
#endif
#define assert_malloc(A) \
defun_assert_lambda (void*, NULL, malloc(A))

....I don't recommend it, for obvious reasons.
Or are we bound to these one-shot do/while loops in order to handle the
trailing ';' ?

Yes, but they only *look* ugly; there's nothing else wrong with them.

--
-trent
On two occasions I have been asked [by members of Parliament], `Pray,
Mr. Babbage, if you put into the machine wrong figures, will the right
answers come out?' I am not able rightly to apprehend the kind of
confusion of ideas that could provoke such a question.
-- Charles Babbage
 
T

Trent Buck

Up spake Paul L Daniels:
Never claimed it to yet be an exercise in English literature.

That is not a valid excuse for spelling or grammar mistakes, or
ambiguous or poor wording.
The book is incomplete and will go through an editing phase by 3rd
parties.

....but this is. While couched a little over-aggressively, the OP's
criticism is valid, and a significant portion of the prose needs to be
rewritten for clarity.
 
J

jacob navia

Paul said:
Hello All,

I've been compiling a series of pages about the bad functions and
mistakes that occur in C into a book, subsequently, I now have a moderate
little book of about 32 pages available for download as a PDF file at

http://www.pldaniels.com/c-of-peril

It's currently still being worked on and I'm always seeking out new
examples of C being used poorly or broken standard functions.

Regards.

This examples aren't strictly new...

Many solutions have been proposed, and it wouldn't hurt if you would
review the literature and tell the reader what has been done to addres
this problems.

The string handling of C, designed more than 3 decades ago, is obviously
a very weak feature of the language, as we have discussed repeatedly
here. Microsoft, (to name one) has proposed a safer string library, and
many other people have proposed others.

The same for the eternal free/realloc/malloc problems. I have been
arguing here for a GC based solution, and there is a lot of other
solutions too.

If you want to contribute something more interesting than an old rehash
of previous discussions, a review of the literature is necessary.
jacob
 
M

Malcolm

Arthur J. O'Dwyer said:
Nope. But why does it matter? 'do' is just as much a part of the
language as the left parenthesis, and it always has been.
It's a minor irritation.
By itself it doesn't matter much, but the cumulative effect of many minor
irritations is that code becomes hard to read, and thus more expensive to
maintain.
 
K

Keith Thompson

Arthur J. O'Dwyer said:
2.3: C++ programmers will not like that you use a struct with the
same name as a function.

In a document about C, there's not much reason to care whether C++
programmers will like it or not. On the other hand, C programmers may
dislike it because it's confusing, even if it's perfectly legal.
2.4: Why 'CP_strncpy' but 'zstrncat'? Is there a method to your
naming convention?
Several of your single-line comments spill over and become syntax
errors; this is exactly why Real Programmers[tm] do not use C++-style
comments.

The best reason to avoid C++-style comments is that they're not (yet)
portable. C++ and C99 support them; C90 does not, though many pre-C99
compilers do support them as an extension.

"//" comments can be a problem in Usenet postings because there are
points between writing the code and reading it where long lines can be
quietly folded by client software that assumes it's dealing with
paragraphs rather than source code. (The same thing can happen with
string literals and macro definitions, but it's the comments that
people complain about.) This shouldn't be an issue in a document like
a PDF file where you (theoretically) control the formatting yourself.

<OT>
Some languages, such as Perl and Ada, only support comments introduced
by a delimiter and terminated by the end of a line. Personally, I
like that better than C's "/* ... */" comments. If "//" comments were
portable in C, I'd probably use them in preference to "/* ... */"
comments. YMMV.
</OT>

[...]
Listing 3.3 is labeled "Example of segmentation faulting using free,"
but it does not exhibit a segfault --- it's perfectly valid. Remove
the '= NULL' initializer and you've got a case.

But even then, a segfault (whatever that is; C doesn't define the
term) is only one possible consequence of undefined behavior.

[...]
6: "programming ... is not the place to express creativity ..."
Obviously you have never programmed!

But there is a valid point there. Certain forms of "creativity" are
inappropriate. (I'd expound further if I had time.)

And "obsfuscated" is misspelled.
 
A

Alex Wade

I'm sorry, but I think you need to do a great deal of proofreading and
correct your spelling, grammar and style. I gather from the number of
errors in the English that it is probably not your first language. Perhaps
you should have somebody who speaks English proofread and correct the
document for you.
 
C

CBFalconer

Malcolm said:
It's a minor irritation. By itself it doesn't matter much, but
the cumulative effect of many minor irritations is that code
becomes hard to read, and thus more expensive to maintain.

Not at all. The purpose of using it is exactly the opposite - to
allow the macro to be used in normal C contexts without creating
obscure syntax errors in the final code, or requiring special
considerations such as adding or omitting semicolons.

As far as reading the original macro definition is concerned, this
is a well known method, and should not cause any concern
whatsoever.
 
A

Arthur J. O'Dwyer

In a document about C, there's not much reason to care whether C++
programmers will like it or not. On the other hand, C programmers may
dislike it because it's confusing, even if it's perfectly legal.

I was being conservative. I imagine /some/ C programmers would like it,
and not think it's confusing at all --- I see why the OP did it, since the
struct is only being used in conjunction with that one function. But it
might confuse C programmers who are /also/ C++ programmers. :)
<OT>
Some languages, such as Perl and Ada, only support comments introduced
by a delimiter and terminated by the end of a line. Personally, I
like that better than C's "/* ... */" comments. If "//" comments were
portable in C, I'd probably use them in preference to "/* ... */"
comments. YMMV.
</OT>

MMDV, yes. Too many formatting pitfalls; also, I like to write block
comments and dislike too many scattered single-line comments.

But there is a valid point there. Certain forms of "creativity" are
inappropriate. (I'd expound further if I had time.)

Certain forms, yeah, but that's a different point. The OP's sentence
was as ridiculous as claiming that "literature is no place for creativity"
just because some people can't spell right!

-Arthur
 
?

=?iso-8859-1?q?Nils_O=2E_Sel=E5sdal?=

I looked quickly through your writeup. It's pretty good.
Here are my remarks on a couple of items that sort of jumped at me.

1.
On the section on free() you wrote:

Certainly it is agreeable that if you are attempting to free a
null pointer, then you're most certainly in a situation where
somewhere prior in your code there is something amiss.

Certainly it is agreeable? Not!

I free() NULL pointers frequently and intentionally. Here is a sample:
You are aware this will utterly **** up things on many platforms ?
I wouldn't advocate this knowing that it may break if I were you ...
 
J

jacob navia

Nils said:
On Sun, 05 Dec 2004 05:11:38 +0000, Rouben Rostamian wrote:
[snip]
I free() NULL pointers frequently and intentionally. Here is a sample:

You are aware this will utterly **** up things on many platforms ?
I wouldn't advocate this knowing that it may break if I were you ...

Please:
<Quote>
The C standard page 312
7.20.3.2 The free function
Synopsis
1 #include <stdlib.h>
void free(void *ptr);
Description
2 The free function causes the space pointed to by ptr to be
deallocated, that is, made available for further allocation.
If ptr is a null pointer, no action occurs.
</quote>
 
R

Rouben Rostamian

You are aware this will utterly **** up things on many platforms ?
I wouldn't advocate this knowing that it may break if I were you ...

free(NULL) is perfectly harmless; it's required to be so by
the C standard.

If your platform is broken, change the platform. Don't ask
me to change my good code to accommodate your bad platform.
 

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,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top