confusion: casting function pointers

B

Beta What

Hello,

I have a question about casting a function pointer. Say I want to make
a generic module (say some ADT implementation) that requires a function
pointer from the 'actual/other modules' that takes arguments of type
(void *) because the ADT must be able to deal with any type of data.

In my actual code, I will code the function to take arguments of their
real types, then when I pass this pointer through an interface
function, I cast the function pointer (just changing the input argument
list to types of void * ).

My question is, when the ADT actually uses the function (inside the ADT
module, all the types are void *) will there be a cast to the 'rea'
types of the arguments (because my code defines the function to take
specific types, like char *) or will there be no cast (because the ADT
is using a function pointer that I had casted as one that takes void *
arguments)?

**** more specific question (example based) ***
What I really have in mind is something like a hash function that we
are sending to a Table ADT module. The hash function in our actual
code takes char * (string) input and returns an int. The Table ADT
wants a function pointer that takes a void * input and returns an int.


Say one defines the hashing function in their code to take a char * and
then casts it as a pointer to a function that takes void *. Then later
when the Table ADT code calls the hashpointer with what it thinks is a
void * (really a char *, but inside Table ADT code it's all void *),
will there be a cast to char *? This is important because the hash
funciton could be using pointer arithmetic on the input parameter.

I could test it on some compilers, but I believe some compilers allow
pointer arithmetic on void * types (as if they are char *).
 
B

Ben Pfaff

Beta What said:
What I really have in mind is something like a hash function that we
are sending to a Table ADT module. The hash function in our actual
code takes char * (string) input and returns an int. The Table ADT
wants a function pointer that takes a void * input and returns an int.


Say one defines the hashing function in their code to take a char * and
then casts it as a pointer to a function that takes void *. Then later
when the Table ADT code calls the hashpointer with what it thinks is a
void * (really a char *, but inside Table ADT code it's all void *),
will there be a cast to char *? This is important because the hash
funciton could be using pointer arithmetic on the input parameter.

I think you are essentially proposing this:

unsigned int string_hash_function(char *) { ... }

void hash_insert(unsigned int (*hash_function)(void *), void *p)
{
unsigned int hash_val = hash_function(p);
...
}

Passing string_hash_function to hash_insert will, in this case,
invoke undefined behavior, because it calls string_hash_function
through an incompatible function pointer.
 
C

Chris Torek

I have a question about casting a function pointer. Say I want to make
a generic module (say some ADT implementation) that requires a function
pointer from the 'actual/other modules' that takes arguments of type
(void *) because the ADT must be able to deal with any type of data.

In my actual code, I will code the function to take arguments of their
real types, then when I pass this pointer through an interface
function, I cast the function pointer (just changing the input argument
list to types of void * ).

Here is what I think you mean, translated into a concrete (if
a bit silly) example:

% cat dbl_module.c
/* this should really be in a .h file, but this is a short example */
extern void (*fptr)(void *, void *, void *);

static void dbl_sum(double *result, double *a, double *b) {
*result = *a + *b;
}

void dbl_init(void) {
fptr = (void (*)(void *, void *, void *))dbl_sum;
}
% cat main.c
#include <stdio.h>

void (*fptr)(void *, void *, void *);

int main(void) {
double x, y = 41.0, z = 1.0;

dbl_init();
(*fptr)(&x, &y, &z); /* line 9 */
printf("x = %f\n", x);
return 0;
}

These two files will compile, but there is no guarantee that the
result will actually work. While it is legal to set "fptr" as
shown in dbl_module.c, the actual call, at line 9 of main.c, passes
three "void *"s that are the result of converting three "double *"s.
The called function -- dbl_sum, in this case -- expects to receive
three "double *"s, not three "void *"s that have "double *" values
encoded somewhere inside them.

(The code will in fact work on most common machines today, but only
because "converting value from Pointer Of Type A to Pointer Of Type
B" is a no-op on those machines, primarily because they have only
one machine-level pointer type.)
My question is, when the ADT actually uses the function (inside the ADT
module, all the types are void *) will there be a cast to the 'rea'
types of the arguments (because my code defines the function to take
specific types, like char *) or will there be no cast (because the ADT
is using a function pointer that I had casted as one that takes void *
arguments)?

It sounds as though you think that a conversion is a cast. This
is not the case, in C at least. A cast is just the syntactic
construct consisting of a type-name enclosed in parentheses
followed by a value. That is:

double x;
int y = 0;

x = y; /* no cast here */
y = 3.1415926; /* no cast here */
y = (int)42; /* there is a cast here */

The first two assignments cause conversions, but there is no cast
involved. The third line causes a conversion from "int" to "int"
due to the cast. (This conversion just changes 42 to 42, i.e.,
has no real effect.)

In any actual function call, there are three items of interest:

- the prototype supplied at the point of the call, if any;
- the actual values supplied at the point of the call, and;
- the actual definition of the target function.

The last of these -- the actual definition of the function that
is being called -- is the sole determiner of what is "correct"
to pass to that function. If the target function is defined
without a prototype, a number of special "promotion" rules take
effect, and then the rest of the process is as if the target
function were defined with a prototype. So I think it is simplest
to assume that the target function will be defined with a
prototype (the promotion rules can be tacked on later; they
get a little complicated).

So, suppose we have the following as a target function:

T0 func(T1 param1, T2 param2, T3 param3) {
...
}

There are four types here: T0, the return type of the function;
and T1 through T3, the three types of the three parameters. There
are exactly three parameters. We can now state the restrictions
on the caller:

- If the caller has a prototype in scope at the point of the call,
that prototype must specify that the called function has
exactly three arguments, that its return type is T0, and
that the three arguments have type T1, T2, and T3 respectively.
The actual arguments will be converted to types T1, T2, and
T3 respectively as if by assignment. If any such assignment
would draw a diagnostic, the call must draw a diagnostic.

- If the caller lacks a prototype, the types T1 through T3 must
be unchanged under promotion, and the actual arguments must
already have the correct type (again after any promotion). In
any case the caller must have provided the type T0 correctly.

(If the target function uses ", ..." and <stdarg.h>, the actual
arguments that correspond to the ",..." part undergo the usual
promotion rules, as if there were no prototype. The sequence
of va_arg() macro invocations used to retrieve them must match
the actual-but-promoted arguments.)

This all brings up a last key point: how does the caller specify
the type of the called function? When calling a function "by name",
in the usual way:

result = func(arg1, arg2, arg3);

it is easy: the prototype comes from the prototype the caller
supplied for "func":

extern T0 func(T1, T2, T3);
...
result = func(arg1, arg2, arg3);

Here, if the prototype match the definition -- presumably it does
-- then the compiler can "vet" the call, converting the actual
arguments as if by assignment so that the converted results appear
in the appropriate formal parameters.

(If the caller forgot to provide a prototype, of course, the old
K&R-1 rules apply, with promotions. If the caller supplied an
incorrect prototype, the behavior is undefined.)

But what happens when the call is indirect, via a function pointer?
In this case, the prototype comes from the type of the function
pointer. So:

T0 (*fp)(T1, T2, T3) = func;
...
result = (*fp)(arg1, arg2, arg3);

This call is correct if and only if "*fp"'s type matches that of
"func" (or, equivalently, if fp's matches that of &func, and of
course "&func" is redundant in C, so there are a lot of ways to
put it).

What you seem to want to do is to store, in "fp", a pointer to
a function-pointer whose type is *not* "T0 (*)(T1, T2, T3)". The
C standard says that you may do this -- via a cast -- under one
condition: you must change the value *back* to the "correct"
type before the call. So, for instance, suppose we have another
function f2():

extern int f2(double);

We can then do this:

T0 (*fp)(T1, T2, T3);
int iresult;

fp = (T0 (*)(T1, T2, T3))f2; /* 1st cast */
iresult = (*(int (*)(double))fp)(42.0); /* 2nd cast */

Here, the prototype at the point of the call is supplied by the
second cast. Since "fp" holds the (converted) pointer to f2(),
and f2() really has type "int (*)(double)", we have to convert
the pointer back before the call.

Using this method, for callback functions in general (such as
for rewriting qsort()), is clumsy at the point of the call:
we have to decide which of some finite set of "actual function
types" the target function really has, and convert our pointer
to the right one. For instance:

typedef void (*hashtable_iterator_string)(const char *);
typedef void (*hashtable_iterator_double)(double);
typedef void (*hashtable_iterator_int_int)(int, int);
enum real_iterator_type { HT_IT_STRING, HT_IT_DOUBLE, HT_IT_INT_INT };

void ht_iterate(struct hashtable *ht, hashtable_iterator_string func,
enum realtype rt) {
const char *string_arg;
double double_arg;
int i1, i2;

for (...) { /* whatever it takes to iterate over entries */
switch (rt) {
case HT_IT_STRING:
/* figure out the string arg */
string_arg = entry->data;
(*func)(string_arg);
break;
case HT_IT_DOUBLE:
/* figure out the double arg */
double_arg = *(double *)entry->data;
(*(hashtable_iterator_double)func)(double_arg);
break;
case HT_IT_INT_INT:
/* figure out the two int args */
i1 = ((int *)entry->data)[0];
i2 = ((int *)entry->data)[1];
(*(hashtable_iterator_int_int)func)(i1, i2);
break;
default:
panic("impossible iterator type %d\n", (int)rt);
}
}
}

There is (usually) a better way. Instead of trying to enumerate
every possible call, simply require that every actual target function
be defined to match a *single*, pre-specified, "sufficiently
flexible" prototype -- such as "void (*)(void *)". Now the
iterator itself looks like this:

typedef void (*hashtable_iterator)(void *);

void ht_iterate(struct hashtable *ht, hashtable_iterator func) {
for (...) { /* whatever it takes to iterate over entries */
(*func)(entry->data);
}
}

Of course, this points the onus on the callee, instead of the
caller. Now, instead of writing:

static void double_iterator(double x) {
sum += x;
}
...
ht_iterate(table, (hashtable_iterator_string)double_iterator,
HT_IT_DOUBLE);

you have to write:

static void double_iterator(void *x0) {
double x = *(double *)x0;
sum += x;
}
...
ht_iterate(table, double_iterator);

But in fact, this is pretty straightforward; and if you distrust
casts (which is a sensible thing to do) you can now even write the
whole thing cast-free:

static void double_iterator(void *xp0) {
double *xp = x0;
sum += *xp;
}

Whenever you have control over the functions that will be called,
this is the method to use. If not, well...
**** more specific question (example based) ***
What I really have in mind is something like a hash function that we
are sending to a Table ADT module. The hash function in our actual
code takes char * (string) input and returns an int. The Table ADT
wants a function pointer that takes a void * input and returns an int.

In this case, you can "cheat". The C standards guarantee that
"char *" and "void *" have the same underlying representation, and
qualifiers (const, volatile, and in C99, "restrict") do not change
this. However, the type-safe thing to do -- the one that allows
you to avoid casts -- is to write a wrapper function.

Suppose, for instance, you have an "iterator" like the original
double_iterator shown above, but for some reason, you cannot alter
it to take a "void *". Suppose further that the function is
complicated enough not to simply rewrite it entirely. Then all
you have to do is write a wrapper:

static void double_iterator(double x) {
... something really complicated ...
}

static void wrapper(void *x0) {
double *xp = x0;
double_iterator(*xp);
}
...
ht_iterate(table, wrapper);

This version is once again clearly type-correct: the caller
(ht_iterate) believes it is calling a "void (*)(void *)", and your
wrapper is in fact a "void (*)(void *)". Your wrapper then obtains
the appropriate "double" and correctly passes it on to the "real"
target.

This method is also the key to writing fancy callback functions
that take multiple parameters. Instead of taking two, three, or
four separate parameters -- consider the HT_IT_INT_INT case above
and expanding it to "one int, one double, and one char *" -- you
simply wrap them all up into a structure, and pass its address as
converted to "void *":

/* stuff from some header file */
struct table; /* from the table module, perhaps */
extern void do_callbacks(struct table *, void (*)(void *), void *);
/* probably also in the table module */

/* stuff local to our own code */
struct args {
int ival;
double dval;
char *str;
/* more if needed */
};

static int callback(void *args0) {
struct args *args = args0;
int result;

/* note that you can use and/or modify any args->member here */

... work with args->ival, args->dval, and args->str ...

return result;
}

void somefunc(void) {
struct args args;
int final_result;

... set up "args" in advance if needed ...
final_result = do_callbacks(table, callback, &args);
... use any results stored in "args" if needed ...
}

The do_callbacks() code, over in some other module, has no idea --
and never *needs* to have any idea -- that callback() is actually
working with a large "struct" that collects up a bunch of data.

(In some other languages, this would be built-in and would be
called a "closure". :) )
 
A

Alex Fraser

Beta What said:
I have a question about casting a function pointer. Say I want to make
a generic module (say some ADT implementation) that requires a function
pointer from the 'actual/other modules' that takes arguments of type
(void *) because the ADT must be able to deal with any type of data.

In my actual code, I will code the function to take arguments of their
real types, then when I pass this pointer through an interface
function, I cast the function pointer (just changing the input argument
list to types of void * ).

A straightforward solution is to define the function with parameters of type
void * (or const void * if desired/appropriate), and convert the arguments
to the real types in the function. This does not require any casts at all,
which is usually a Good Thing.

[snip]
**** more specific question (example based) ***
What I really have in mind is something like a hash function that we
are sending to a Table ADT module. The hash function in our actual
code takes char * (string) input and returns an int. The Table ADT
wants a function pointer that takes a void * input and returns an int.

Following my general advice above:

int hash_func(void *vp) {
/* convert argument to its real type: */
char *p = vp;

/* compute hash: */
int hash_value = 0;
/* ... apply hashing algorithm ... */
return hash_value;
}

Alternatively:

int real_hash_func(char *p);

int hash_func(void *vp) {
return real_hash_func(vp); /* void * to char * automatic due to
prototype */
}

See also <http://c-faq.com/lib/qsort2.html>.

Hope this helps,
Alex
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top