New book - 'C of Peril'

T

Trent Buck

Up spake Paul L Daniels:
What struck me hardest though was that the text was actually run through a
spell checker (via LyX). Obviously it was insufficient.

FWIW, most of the corrections I sent you were caught by both spell and
ispell (GNU flavours). ispell understands and ignores TeX markup.
 
T

Thomas Stegen

Keith said:
I didn't read quite carefully enough. Your free() macro means that
any calls to free() will do nothing. But that still leaves the
problem of letting the garbage collector know that a pointer is no
longer being used.

Some of the techniques that can be deployed are described here:
http://www.hpl.hp.com/personal/Hans_Boehm/gc/

Note that there is no need for resetting pointers unless you store
them in a datastructure with dynamic extent. Though this is the same
for all GC languages including for example Java and Common Lisp.

Just setting the pointer to NULL is sufficient. Pointers that go out of
scope or otherwise dies can be ignored.

The benefit then is that you can reason about lifetime locally instead
of globally. Without GC one must consider any possible usage of an
object before freeing it. With GC one only needs to consider local usage
and then set the pointer to NULL if it is no longer needed, the global
concern is taken care of by the garbage collector.
 
A

Arthur J. O'Dwyer

I think the first thing I should do before expanding anything else is
list Arthur as a "Contributor" and credit him ;-)

Bah. You asked for corrections, and I supplied some. No worries.

-Arthur
 
D

Dan Pop

In said:
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".

Now, replace malloc by fopen and free by fclose and see what happens:
your code is as sensible as in its original version, but it is broken now.

The real problem is that C is inconsistent in this area. Either make
*both* free(0) and fclose(0) legal or outlaw both of them.
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)

Why bother?

#define foo(x) (bar(x), baz(x))

will do.

Dan
 
D

Dan Pop

In said:
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.

When was a consensus reached on this one?
Microsoft, (to name one) has proposed a safer string library, and
many other people have proposed others.

But none has caught up! It seems that the good old C string library is
not that bad, after all. Its obvious advantages are:

1. It is very simple and easy to understand.

2. It is perfectly possible to use it with 100% safety, if you know what
you're doing. It's also quite easy.
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.

And, again, the C programming community keeps using malloc and friends.
So, apart from problems, they *must* also have some advantages, too.

By the time you'll start reading at least 10 times more code than you're
writing, you may start appreciating the merits of code relying on the
standard C library as much as possible as opposed to code that reinvents
every possible wheel. The latter is the nightmare of any maintainer.

Dan
 
D

Dan Pop

In said:
That's not to say that it would be unreasonable to provide garbage
collection in a C implementation, but it would be a change in the
semantics of the language.

And there would be no way to get it right if implemented exclusively
as a library feature, without help from the compiler.

Dan
 
R

Richard Bos

Arthur J. O'Dwyer said:
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).

TTBOMK, yes, it does, and boy is that code ugly! Effective, but a
massive rat-king of kludges.

Richard
 
A

Arthur J. O'Dwyer

Why bother?

#define foo(x) (bar(x), baz(x))

What if 'bar' and 'baz' are statement-like macros in their own right?
If they are, then Paul's code is "correct" but has pitfalls; Rouben's
code is correct; and your code is wrong. If they're function-like macros
(or real functions), then the OP's code was overkill to begin with.
Nitpicky, but the OP /was/ talking about the pitfalls of C and how to
avoid them. If he takes your advice, he ought to note that

#define bar(x) if ((x)==42) break

would screw it up.
In fact, that's a C puzzler for the group: The above #definition of
'bar' is bad because of

if (a) bar(b); else c();

Does changing the macro to

#define bar(x) if ((x)==42) break; else ((void)0)

fix it? If not, can you think of any way to fix it, while retaining the
macro's effect (breaking out of the nearest enclosing loop, without using
any extra goto-labels or other such tricks)?

-Arthur
 
C

CBFalconer

Dan said:
(Rouben Rostamian) writes:
.... snip ...

Now, replace malloc by fopen and free by fclose and see what
happens: your code is as sensible as in its original version,
but it is broken now.

The real problem is that C is inconsistent in this area.
Either make *both* free(0) and fclose(0) legal or outlaw both
of them.

It is not just this area, but in the more fundamental areas of
syntax, precedence, etc. Unfortunately we have to live with it, as
correction would cause more havoc than continuation. The
alternative is languages with more self-consistency, such as Ada
and Pascal. C++ manages to combine the worst of most worlds. IMO.
 
K

Keith Thompson

Thomas Stegen said:
Some of the techniques that can be deployed are described here:
http://www.hpl.hp.com/personal/Hans_Boehm/gc/

Thanks, I'll take a look at that when I have time.
Note that there is no need for resetting pointers unless you store
them in a datastructure with dynamic extent. Though this is the same
for all GC languages including for example Java and Common Lisp.

Just setting the pointer to NULL is sufficient. Pointers that go out of
scope or otherwise dies can be ignored.

The benefit then is that you can reason about lifetime locally instead
of globally. Without GC one must consider any possible usage of an
object before freeing it. With GC one only needs to consider local usage
and then set the pointer to NULL if it is no longer needed, the global
concern is taken care of by the garbage collector.

My point is that a program written for a system without GC cannot
necessarily be used with GC just by changing the runtime library. You
can replace malloc() with a GC-aware allocator and replace free() with
a no-op, but this can still (in some cases) prevent some garbage from
being collected. A standard C program can sensibly allocate a large
data structure (with the root pointed to by a global variable), then
recursively free() all the nodes and leave the (now indeterminate)
root pointer alone; if the program isn't going to use it again,
there's no reason to set it to NULL. Of course if the root pointer is
a local variable, the nodes can be garbage-collected after it goes out
of scope.

I'm not arguing that GC is a bad idea, or that it's unacceptably
difficult to use it, just that it doesn't *quite* fit seamlessly into
standard C.
 
M

Malcolm

CBFalconer said:
As far as reading the original macro definition is concerned, this
is a well known method, and should not cause any concern
whatsoever.
The fault lies with the language, rather than the original programmer.

Some construct such as #define foo(x,y){ code(x); morecode(y); } should be
used to define multi-line macros. Whoever designed the original preprocessor
forgot to include any support, and so we are stuck with the do while
workaround.

As for being a well-known method, it is a little quirk that experienced C
programmers will eventually pick up, but might puzzle programmers who only
use C rarely.
 
K

Keith Thompson

Malcolm said:
The fault lies with the language, rather than the original programmer.

Some construct such as #define foo(x,y){ code(x); morecode(y); } should be
used to define multi-line macros. Whoever designed the original preprocessor
forgot to include any support, and so we are stuck with the do while
workaround.

The preprocessor expands macro invocations to arbitrary sequences of
tokens; it has no concept of higher-level constructs such as
statements and blocks. This makes it both powerful and dangerous.

It's not the preprocessor's fault that

#define foo(x,y){ code(x); morecode(y); }

doesn't always work. It's a consequence of the grammar of the
language itself, specifically the fact that a block cannot always be
substituted for a statement.
As for being a well-known method, it is a little quirk that experienced C
programmers will eventually pick up, but might puzzle programmers who only
use C rarely.

The "do { ... } while(0)" idiom is obscure at first glance, but *is*
well-known, and any C programmer should be able to understand it.
It's even in the FAQ (10.4). Programmers who only use C rarely are
likel to have bigger problems than understanding a do-while loop in a
macro definition.
 
P

Paul L Daniels

Bah. You asked for corrections, and I supplied some. No worries.

Exactly. I do believe it would be only fair that you are duly noted in
the credits since you did provide quite an number of corrections.
 
L

Lawrence Kirby

As I said, the proposed defines themselves are illegal.

Only as part of program code. Just arrange for this to be considered part
of the implementation and that problem goes away.

Lawrence
 
D

Dan Pop

What if 'bar' and 'baz' are statement-like macros in their own right?

Unless the description of the problem *explicitly* mentions this
possibility, there is no point in bothering to work around it.

Dan
 
C

CBFalconer

Lawrence said:
Only as part of program code. Just arrange for this to be
considered part of the implementation and that problem goes away.

True, but so does standards conformance. See the "obfuscated
pointer" problem mentioned elsethread. The result is just another
bastard language.
 
L

Lawrence Kirby

On Wed, 08 Dec 2004 15:26:36 +0000, CBFalconer wrote:

....
True, but so does standards conformance. See the "obfuscated
pointer" problem mentioned elsethread. The result is just another
bastard language.

Indeed, which is why I stressed it with "that" problem. Clearly there are
other problems which do not go away.

Lawrence
 

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

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,281
Latest member
Pedroaciny

Latest Threads

Top