Function pointer question P119 K&R

M

mdh

May I ask the following.

By K&R's own admission, the example used to describe function pointers
is complex ( on P119). In addition, the use of casts has been stated
by some on this group as being, again, a poor/bad example of it's use.
For the moment, accepting these criticisms, I would still like to get
some insight into why/how some things work as even poor code is
enlightening, to me at least.

The example uses K&R's version of qsort ( not the standard one) and
the declaration is as follows:

void qsort(void *lineptr[], int left, int right. int (*comp)(void *,
void *));

The example then chooses from one of 2 methods for comparing strings,
whose declaration are:

int numcmp (*char, *char);
int strcmp( *char, *char);

The call to qsort is as follows:

qsort (void**)lineptr, 0, nlines-1, int (*)(void*, void*)) numeric?
numcmp:strcm)); /* numeric is an integer set based on an optional
command line argument) */

One last declaration before my question.

The swap function is declared as:

void swap(void *[], int, int);

Within the qsort function, the call to the function pointer is as
follows:

if ((*comp)(v, v
) < 0)
swap (v, ++last, i);

If qsort casts the comparison function to void pointers, as it does in
"main", then why is it ok for the call in qsort to pass as arguments
to void pointers when the functions numcmp/strcmp expect as arguments
2 char pointers? Or..I might be totally confusing the issue, which is
equally likely. Swap seems to act as I would expect, ie it receives as
it's arguments, an array of pointers to void, plus 2 integers.

thanks in advance.​
 
B

Ben Bacarisse

The example uses K&R's version of qsort ( not the standard one) and
the declaration is as follows:

void qsort(void *lineptr[], int left, int right. int (*comp)(void *,
void *));

The example then chooses from one of 2 methods for comparing strings,
whose declaration are:

int numcmp (*char, *char);
int strcmp( *char, *char);

The call to qsort is as follows:

qsort (void**)lineptr, 0, nlines-1, int (*)(void*, void*)) numeric?
numcmp:strcm)); /* numeric is an integer set based on an optional
command line argument) */

One last declaration before my question.

The swap function is declared as:

void swap(void *[], int, int);

Within the qsort function, the call to the function pointer is as
follows:

if ((*comp)(v, v
) < 0)
swap (v, ++last, i);​


We now have v. The prototype you showed had lineptr. I'll assume the
v is a void ** like lineptr.
If qsort casts the comparison function to void pointers, as it does in
"main", then why is it ok for the call in qsort to pass as arguments
to void pointers when the functions numcmp/strcmp expect as arguments
2 char pointers?

It is not OK (in general). It works in practise because void * and
char * must have the same size and representation but if the lineptr
array were, say, an array of struct pointers it might very well fail
on a real machine (albeit an old one).

You can convert a function pointer to any type of function pointer you
like but to be safe, when you call it, you have converted it back to
(a pointer to) the type of the function you are actually calling and
you must pass arguments that are "acceptable" as per the normal
function call rules. I say "acceptable" because the exact rules are
rather wordy and are not really the subject of our question.
Or..I might be totally confusing the issue, which is
equally likely. Swap seems to act as I would expect, ie it receives as
it's arguments, an array of pointers to void, plus 2 integers.

Yes, the call to swap is fine.​
 
S

santosh

m said:
Ben, you say that void * and char * must have the same size and
representation. Are you saying that this is a fact in C, or are you
saying that it must be so for the program to work as intended and it
is up to the programmer to ensure that this fact is true.

No, it is required by the Standard. This was because before
Standardisation, char* was often used as a "generic" pointer type. The
Standard codified a separate type for this (void*), but to maintain
compatibility with pre-Standard code and ease the transition to
Standard C, it retained this property of char* too.

IIRC K&R2 explains this in appendix A.
 
R

rahul

int numcmp (*char, *char);
int strcmp( *char, *char);

int numcmp(char *, char *);
int strcmp(char *, char *);

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

I assume you meant it to be:
qsort ((void**)lineptr, 0, nlines-1, (int (*)(void*, void*)) (numeric?
numcmp:strcm));


If qsort casts the comparison function to void pointers, as it does in
"main",
qsort is casting the function pointer to a pointer to a function
taking 2 pointer to a void parameters and returning int.
to void pointers when the functions numcmp/strcmp expect as arguments
2 char pointers?
char and void pointers are required to have compatible representation.
Still, the correct way to do it is to declare numcmp as taking void *
and cast it in the function itself.

int numcmp(void *foo, void *bar) {
return *(int *)foo - *(int *)bar;
}
 
B

Ben Bacarisse

m said:
Thank you Richard. At some point...I would really like to hear your
take on the issue of the misuse of casts.

But it is probably worth pointing out that the code is still a
technical violation. The wording about representations and alignment
mean that it is hard to see what an implementation could be doing that
would make the code go wrong but it is still, technically, UB:

6.5.2.2 p9: "If the function is defined with a type that is not
compatible with the type (of the expression) pointed to by the
expression that denotes the called function, the behavior is
undefined."

You need to read an awful lot to be sure, but the function types
pointed to by 'int (*)(void *, void *)' and 'int (*)(char *, char *)'
are not compatible -- even allowing for the special wording about char
* and void *.
 
M

mdh

m said:





Buried deep in 3.1.2.5 we find that "A pointer to void shall have the same
representation and alignment requirements as a pointer to a character
type.  Other pointer types need not have the same representation or
alignment requirements."



Not to flog a dead horse, does this then mean that when a void pointer
is passed to function (foo) expecting a character pointer, foo simply
cannot tell the difference between what it is expecting and what it is
getting, or that the compiler does not complain as **it** knows that
there is no difference between the two and thus allows this step to
occur without any warning?
 
M

mdh

It doesn't mean either of those things. When a void pointer is passed to a
function that expects a character pointer, C provides an automatic
conversion from void * to char *, just as it would between void * and any
other pointer-to-object or pointer-to-incomplete-type. In this situation
the representational similarities of void * and char * are irrelevant
(although they obviously make the conversion very simple for the
implementation to achieve).


thanks Richard
 
B

Barry Schwarz

Not to flog a dead horse, does this then mean that when a void pointer
is passed to function (foo) expecting a character pointer, foo simply
cannot tell the difference between what it is expecting and what it is
getting, or that the compiler does not complain as **it** knows that
there is no difference between the two and thus allows this step to
occur without any warning?

There is a difference between the function knowing what to expect and
the compiler knowing what the function expects. If the prototype
tells the compiler the function is expecting a non-void object
pointer, then any void* whose value is properly aligned for the object
type will be converted automatically. Similarly, if the compiler
knows that the function is expecting a void*, then any object pointer
will be converted automatically. This is a result only of the
implicit conversion between void* and object pointer and has nothing
to do with the mandatory similarity between void* and char*.

On the other hand, consider the case of a variadic function like
printf. When processing a %p format specification, the function
expects a void*. However, the compiler has no idea and will pass a
char* without modification. (Before someone notes that some compilers
do in fact check the format string, I remind them that the format
string need not be a literal and the compiler may not know it's
contents.) In the unlikely event that there is a different passing
mechanism for void* and char*, you have undefined behavior in spite of
the mandatory similarity between the two.


Remove del for email
 
M

mdh

There is a difference between the function knowing what to expect and
the compiler knowing what the function expects.  If the prototype
tells the compiler the function is expecting a non-void object
pointer, then any void* whose value is properly aligned for the object
type will be converted automatically.  Similarly, if the compiler
knows that the function is expecting a void*, then any object pointer
will be converted automatically.  This is a result only of the
implicit conversion between void* and object pointer and has nothing
to do with the mandatory similarity between void* and char*.

On the other hand, consider the case of a variadic function like
printf.  When processing a %p format specification, the function
expects a void*.  However, the compiler has no idea and will pass a
char* without modification.  (Before someone notes that some compilers
do in fact check the format string, I remind them that the format
string need not be a literal and the compiler may not know it's
contents.)  In the unlikely event that there is a different passing
mechanism for void* and char*, you have undefined behavior in spite of
the mandatory similarity between the two.

Remove del for email

Barry, thank you for that erudite explanation. It really adds to my
understanding of C.
 
K

Keith Thompson

Barry Schwarz said:
On the other hand, consider the case of a variadic function like
printf. When processing a %p format specification, the function
expects a void*. However, the compiler has no idea and will pass a
char* without modification. (Before someone notes that some compilers
do in fact check the format string, I remind them that the format
string need not be a literal and the compiler may not know it's
contents.)
[...]

And even if a compiler does check the contents of the format string
(in the special, though very common, case that it's a string literal),
it won't necessarily use that information to affect the code it
generates; it could just use it to issue a warning if there's a
mismatch. <OT>As far as I know, that's what gcc does, but I haven't
looked at its innards.</OT>

Note that it *could* do so. For example, given:
printf("%f\n", 42);
a compiler that examines the format string and detects the error would
know that the behavior is undefined. It can therefore do anything it
likes, including changing the format string to "%d\n" so the code
works as the author (presumably) intended. But this would be a bad
idea; it would encourage programmers to leave bad code like this alone
rather than fixing it.
 
C

Chris Torek

Not to flog a dead horse, does this then mean that when a void pointer
is passed to function (foo) expecting a character pointer, foo simply
cannot tell the difference between what it is expecting and what it is
getting, or that the compiler does not complain as **it** knows that
there is no difference between the two and thus allows this step to
occur without any warning?

Others have given various explanations but I would like to attempt
one that may help even more.

Think of each invocation of a C compiler as a request for a whole
new person to come in and do some work.

% cc -c a.c
% cc -c b.c
% cc -c c.c
% cc -o myprog a.o b.o c.o

The first time you run it, you get Alice. She comes in and
looks at a.c. She believes everything it says, and produces a.o.

The next time, though, you get Bob. He comes in and looks at b.c.
He believes everything it says, and produces b.o.

Your third command brings in Christine, who looks at c.c and
produces c.o.

Finally, your last one brings in Dave, who looks only at a.o, b.o,
and c.o and produces your program "myprog".

In principle, Alice, Bob, and Christine could even be in different
countries, and all working at the same time without ever meeting
or knowing each other. Dave, who does what is usually called the
"link" phase, does at least get to see whatever work-notes they
leave behind, but not the source files.

So, when you say "a void pointer is passed", "foo is expecting",
and "the compiler ... it knows", we have a problem. Is Alice doing
the passing? Is Bob doing the expecting? There are four separate
"compilers" here. Which one(s) know what?

Since -- at least in principle -- none of these separate invocations
needs to communicate much with each other, and even "Dave" (the
linker) may not get to see much, because Standard C requires very
few "work notes" to be left for him, it is your responsibility, as
the C programmer, to make sure you never tell Alice, Bob, or
Christine any lies. Everything that a.c claims about b.c and c.c
had better be true and correct; everything that b.c claims about
a.c had better be true and correct; and everything that c.c claims
about a.c and b.c had better be true and correct.

The easiest way to do this is to use header files. Have all three
".c" files pull in some common header(s): all.h; or a.h, b.h, and
c.h; or some combination like this. Make sure that any functions
that a.c exposes (i.e., makes public) are declared in a.h or all.h
or whatever, *and* that a.c #includes that file, so that Alice,
who works on a.c, will compare the "public claim" in a.h against
the actual function in a.c. This lets Alice tell you if you goofed
here.

There are two main things you can claim about a function, when you
give the function's name:

- its return type, and
- the number and type(s) of its arguments (to the extent that
those arguments are fixed, at least -- this brings up a third
thing you can claim, i.e., "all fixed arguments" vs "variadic").

To make both claims, use a prototype. Using a non-prototype
declaration like:

double blah();

makes only a claim about its return type. (Well, it also makes
the claim that the arguments are non-variadic, but it makes this
very weakly.) Unlike another apparently-similar language, if
blah() takes no arguments at all, you *must* put "void" inside
the parentheses to say this:

double blah0(void); /* blah0 takes exactly 0 arguments */
double blah2(double, char *); /* blah2 takes 2 args, as shown */

If the only thing any particular compiler / person (Alice, Bob,
or Christine in this case) sees about a function is a prototype,
she, he, or it will simply believe what the prototype says. So
if Alice thinks blah2() needs one "double" and one "char *",
and you do this:

double x = blah2(42, 0);

Alice will assume that you meant to send 42.0 and (char *)NULL to
blah2(), and will arrange for that to happen.

She need not leave any note for Dave to say that she did this, so
if blah2() is in b.c or c.c, and does not *actually* take one
"double" and one "char *", bad things may happen later. But if
blah2() is in b.c, and the prototype for blah2() is in b.h, and
b.c says to #include "b.h", then at least Bob will get a chance to
spot the mistake in b.h, and tell you. And then when you correct
b.h, you will -- or at least "should" -- know to ask Alice (or
maybe it will be Arnold) to compile a.c again, as well as getting
Bob (or Belinda this time) to compile b.c.

(You should know this because you should be keeping track of the
fact that a.c depends on b.h, so that if you touch b.h, you must
recompile a.c. Some build systems will do this automatically for
you. Some require a bit of manual work on a "makefile" and/or a
"make depend" step, and some really primitive systems make you
remember everything yourself and re-type your compilation commands
every time.)

In a particularly nice system, "Alice" would leave a note (in a.o)
for "Dave" (the linker) saying "by the way, I assumed this that
and the other thing" and Dave would check her notes against everyone
else's, and tell you if you got stuff wrong. Systems this nice
are still pretty rare, unfortunately.
 
M

mdh

Others have given various explanations but I would like to attempt
one that may help even more.


Thank you very much Chris. That did enlighten me, and I am sure
others.
 

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,754
Messages
2,569,528
Members
45,000
Latest member
MurrayKeync

Latest Threads

Top