Fog Creek Software
Discussion Board




K&R. Great book,but boy they slip some doozies in.

Take this for example, in the section on function pointers:

qsort( (void **)lineptr, 0, nlines -1,
( int (*)(void*, void*))(numeric ? numcmp : strcmp )  );

Of course the last argument determines whether to sort lexigraphically or numerically. 

Anyway for someone learning to use C The Right Way, tossing in *unexplained* examples like this is extremely confusing.  This is the type of thing they really should spend some time breaking down.  (BTW, their overall explanation for function pointers is just bad).

Also, why didn't K&R just write the call like this:

qsort( (void**)lineptr, 0, nlines -1, ( numeric ? numcmp : strcmp ) );

Doesn't this do exactly the same thing, only much clearer?

Thanks again!
Confused

Confused
Saturday, July 10, 2004

It's not quite the same, because strcmp (assuming they used the standard library version) is actually prototyped as:

  int strcmp( char *a, char *b );

while qsort is expecting:

  int (*cmpfunction)( void *a, void *b )

Notice the different parameter types. So they need the cast in there for it to compile properly.

In "classic" pre-ANSI C, they wouldn't have needed the cast, and gotten a whole other range of nasty problems as the trade-off. :-)

Chris Tavares
Saturday, July 10, 2004

Confused,

it's an outrageous hack and they are showing off. It's part of the reason the K&R is so great - it has a number of mindbenders like that one.

The reasonable thing to do in any serious production envirenment would be to write clear code and stay away from such craziness. But we all like to include a few puzzlers from time to time just to keep the new guys on their feet.

Dennis Atkins
Saturday, July 10, 2004

In their particular production environment, they used exactly the right solution. Their version will compile correctly without beefing under any compiler. C++ compilers, in particular, would require the form that they presented and would choke otherwise.  Stroustrupe holds up the examples in that book as good examples of how to design C++ functions.

Clay Dowling
Sunday, July 11, 2004

I disagree. I claim that as a first refactoring the following code is superior:

  typedef int (*COMPARISON_FUNCTION)(void *, void *);

  COMPARISON_FUNCTION my_compare;
  my_compare = numeric ? numcmp : strcmp;

  qsort( (void **)lineptr, 0, nlines-1, my_compare);

Dennis Atkins
Sunday, July 11, 2004

I never understood that one-liner mentality, except for playing mind games with people. Sometimes it's called golf; see how few (key)strokes you can use.

Dennis gives a good example of clear, readable code. The other one is obfuscated for no good reason at all. And I won't mention perl...

Tom H
Sunday, July 11, 2004

Response to everyone, Clay and Dennis:

To Everyone:

Part of the problem is that at no point during the book do K&R mention that these forms are equivalent:

1:  ( int (*)(void*, void*) )numcmp
(This is the reduced form of the example where numcmp is used).

and

2:  int (* numcmp)(void *, void * )

Furthermore, we have to deduce that in a obfuscated situation.  Are they trying to teach or impress themselves with their own brilliance?! :)

--------------------------------------------

Clay Dowling:

"Their version will compile correctly without beefing under any compiler. C++ compilers, in particular, would require the form that they presented and would choke otherwise."

This is not mentioned as a reason, and as far as I can tell, there's no proof that this is the case.

-------------------------------------------------------
Dennis Atkins:

I agree, your code is much clearer.

Confused
Sunday, July 11, 2004


"Their version will compile correctly without beefing under any compiler. C++ compilers, in particular, would require the form that they presented and would choke otherwise."

>This is not mentioned as a reason, and as far as I can tell, there's no proof that this is the case.<

In the book's preface, the authors say "We used Bjarne Stroustrup's C++ translator extensively for local testing of our programs, and Dave Kristol provided us with an ANSI C compiler for final testing." This suggests I think that compatibility with a variety of compilers indeed was a concern.

ME
Sunday, July 11, 2004

ME:

I'm sure compiler compatibility was a concern, I'm not arguing that.  What I'm arguing is the idea that that mangled line of code was necessary to maintain compiler compatibility.

Confused
Sunday, July 11, 2004

Dennis's example may look nicer, but it won't compile as written! The types:

int (*)(void *, void *) (pointer to function taking two void *'s, and returning int)

and

int (*)( char *, char ) (pointer to function taking two char *'s and returning int)

are NOT COMPATIBLE ACCORDING TO THE TYPE SYSTEM!

If you don't have a cast in there SOMEWHERE, any decent compiler will complain at you!

Dennis's version needs to be:

  COMPARISON_FUNCTION my_compare;
  my_compare = numeric ? numcmp : (COMPARISON_FUNCTION)strcmp;

Chris Tavares
Sunday, July 11, 2004

Chris:

Aha!  I think I just got it.  I just didn't notice that the function pointer was being used to upcast to something else.  K&R actually explained this (...kinda), but it was seemed so convoluted (and I'd never seen the function pointer cast before) that I just left confused.  Now it makes sense. 

And yes, I agree, you almost certainly need the explicit upcast from char * to void * (and back again) somewhere.

So basically I knew  you could cast with "regular" pointers, but didn't realize function pointers could be cast too.

-A little less confused

Confused
Sunday, July 11, 2004

qsort's cmp function actually takes two pointers to const void. So the cast is doubly dodgy.

(And you'd better be sure void * and char * are the same size, and can be passed in the same way, or you'll have a problem.)

Tom
Sunday, July 11, 2004

1. Yeah, gcc gives a warning on the cast, so put it in there. It compiles fine though even without it.

2. The consts got added to qsort in C99 was it? after the classic X&R 2d ed, but there must be a new addition out, is that it? In any case they are not a problem since you are casting up to consts and that is permitted and no warning or casts are necessary.

3. This whole 'gotta make sure the pointers are the same size' thing seems such an extremeist for of hyper-pedanticalism. IS there even one processor currently being manufactured in which this is an issue? I say no, and the C student is welcome to assume all pointers are the same size in every reasonable situation imaginable. Could someone build a processor in whcih char pointers and int points were different sizes, with chars stored in one part of memory and ints in another? Sure they could but let me tell you that just ain't gonna happen unless teh processor designer is a total retard.

Dennis Atkins
Sunday, July 11, 2004

Oh and yes it will compile as written on standard compliant compilers. And it will run correctly too!

Dennis Atkins
Sunday, July 11, 2004

The library version of qsort had the const qualifiers even in the original ANSI standard, and is documented as such on p253 of K&R2. The section on function pointers uses a hand-crafted version which need not have the same signature as the library version. I'll grant that this is a bit confusing if you're not paying attention.

I hate to disagree with a man with such sound views on const as Dennis, but I think the original version in the book is already clear enough. The reason for the cast is explained perfectly clearly, albeit in only two sentences rather than the 50 pages that Steve McConnell would have required: "The elaborate cast of the function argument casts the arguments of the comparison function. These will generally have no effect on actual representation, but assure the compiler that all is well."

Grumpy old fart
Sunday, July 11, 2004

In C, you're pretty damn safe with pointer sizes.  But C++ offers all kinds of complications:

http://weblogs.asp.net/oldnewthing/archive/2004/02/09/70002.aspx

Almost Anonymous
Sunday, July 11, 2004

OK um well regarding that - MS has decided to take a structure and call in a pointer.

But it's not really a pointer, It's a structure.

Dennis Atkins
Sunday, July 11, 2004

Dennis, a bit off topic but on ARM I think that the register-load can only take 32-bit aligned values, and characters aren't 32-bit aligned.  The compiler has to do all kinds of shifts etc to unpack a character.

i like i
Monday, July 12, 2004

You mean the address is a multiple of 4. So I guess theoretically that's sort of like saying the address is 2 bits narrower than it appears. But I think the whole vaule of the address is still used regardless, right? It doesn't actually use physically different sized values for dealing with char addresses.

Dennis Atkins
Monday, July 12, 2004

Actually, (void*) is a special kind of pointer in that any other type of pointers could be casted into it guaranteered. Read appendix A :)

Like Object in Java.

Back to the question of which style is better.

Dennis's version introduce two temporary variables -- COMPARISON_FUNCTION and my_compare. I would say we don't need my_compare. Just introduce the typedef .

typedef int (*COMPARISON_FUNCTION)(void *, void *);

  qsort( (void **)lineptr, 0, nlines-1, (COMPARISON_FUNCTION)(numeric ? numcmp : strcmp));

Rick Tang
Monday, July 12, 2004

Yes I am aware of the semantics of void pointers in C and yes I have read all the Appendices and yes I have used the grammar to write my own C compiler etc.

Yeah your version is fine. There are plenty of ways to make it more readible. In this case, there's not going to actually be any more temp variables than you would have had anyway unless you've turned off compiler optimizations.

For situations where ordinary folks are going to read and maintain it, i'd even toss out the ternary operator and use an if else.

Dennis Atkins
Monday, July 12, 2004

"Hyper-pedanticalism"? I think you mean "hyper-pendanticism". And you'd better sound a bit surer than that if you're going to make such blanket assumptions :)

(Actually, I'm less convinced than I was when I posted that C allows this kind of thing. I think it is permissible in C++, though.)

Tom
Monday, July 12, 2004

I think this is the third time that the "hyper-pedanticalism" troll has worked. An old one, but it still keeps 'em entertained! Share the joy all!

Dennis Atkins
Monday, July 12, 2004

And now I'm glad I didn't put the smiley in. Karmic balance restored.

P.S. it is actually "hyper-pedantry".

Tom
Monday, July 12, 2004

*  Recent Topics

*  Fog Creek Home