Functions taking pointers to different types as arguments

M

Mikhail Teterin

Hello!

I'd like to have a variable of a pointer-to-function type. The two possible
values are of type (*)(FILE *) and (*)(void *). For example:

getter = straight ? fgetc : gzgetc;
[...]
nextchar = getter(file);

What type should I give to `getter' so that the compiler does not issue
warnings about an incompatible value getting assigned to it? Thanks!

-mi
 
K

Karthik Kumar

Mikhail said:
Hello!

I'd like to have a variable of a pointer-to-function type.

And what is the type of the function that you are referring to here.

For eg, you can use - PTR_TO_FUNC if you want a pointer
to a function with the prototype as that of fgetc as follows.

typedef int ( * PTR_TO_FUNC) ( FILE * );
PTR_TO_FUNC getter;
The two possible
values are of type (*)(FILE *) and (*)(void *). For example:

getter = straight ? fgetc : gzgetc;
[...]
nextchar = getter(file);

What type should I give to `getter' so that the compiler does not issue
warnings about an incompatible value getting assigned to it? Thanks!

-mi
 
C

Chris Torek

I'd like to have a variable of a pointer-to-function type. The two possible
values are of type (*)(FILE *) and (*)(void *). For example:

getter = straight ? fgetc : gzgetc;
[...]
nextchar = getter(file);

What type should I give to `getter' so that the compiler does not issue
warnings about an incompatible value getting assigned to it? Thanks!

The problem is isomorphic to:

"What kind of pointer should I use to point to either a char
or a double, so that I can write *p = 3 to set the char to
control-C or the double to 3.0?"

The answer is: there is no such pointer. You must change the
problem.

In this case, the solution to the problem is to write a wrapper
function. Since we know "void *" is a general-purpose data
pointer, write a wrapper for fgetc():

int wrap_fgetc(void *vp) {
FILE *fp = vp;

return fgetc(fp);
}

Assuming gzgetc() has type "int gzgetc(void *)", you now have two
"int (void *)" functions, and can point to either one with:

int (*fp)(void *) = straight ? wrap_fgetc : gzgetc;

Of course, you must also convert the argument as well:

void *arg = straight ? the_fgetc_file : the_gzgetc_voidstar;

If your second function (gzgetc, here) had some other pointer
type as its sole parameter, you could wrap both functions, because
"void *" will always be able to hold any other pointer.
 
L

lawrence.jones

Mikhail Teterin said:
I'd like to have a variable of a pointer-to-function type. The two possible
values are of type (*)(FILE *) and (*)(void *). For example:

getter = straight ? fgetc : gzgetc;
[...]
nextchar = getter(file);

What type should I give to `getter' so that the compiler does not issue
warnings about an incompatible value getting assigned to it? Thanks!

There's no such type. What you're trying to do is not possible in
portable code. There's no guarantee that FILE * and void * are even the
same size let alone have the same representation, so you can't have a
single call that can invoke either one, you have to have separate calls
or write a wrapper function for one of the functions that has the same
type as the other function.

-Larry Jones

Let's pretend I already feel terrible about it, and that you
don't need to rub it in any more. -- Calvin
 
M

Mikhail Teterin

I'd like to have a variable of a pointer-to-function type. The two
possible values are of type (*)(FILE *) and (*)(void *). For example:

getter = straight ? fgetc : gzgetc;
[...]
nextchar = getter(file);

What type should I give to `getter' so that the compiler does not issue
warnings about an incompatible value getting assigned to it? Thanks!
There's no such type. What you're trying to do is not possible in
portable code. There's no guarantee that FILE * and void * are even the
same size let alone have the same representation, so you can't have a
single call that can invoke either one, you have to have separate calls
or write a wrapper function for one of the functions that has the same
type as the other function.

The standard definition of qsort() seems to disagree with you here. The
callbacks passed to qsort are expected to take `const void *' arguments and
cast them internally.

Just as these callbacks know the true type of their arguments, both fgetc
and gzgetc in my example know, what's passed to them -- and my program
knows, which pointer to use.

As for pointers to different data-types being of different size, this is,
probably, even rarer than the (in)famous non-zero NULL pointer, is it not?
In my case, both functions (fgetc and gzgetc) expect a pointer to a
structure.

That said, a wrapper would, probably, be acceptable, as it will be optimized
away anyway :)

Yours,

-mi
 
M

Mikhail Teterin

Chris said:
The problem is isomorphic to:

"What kind of pointer should I use to point to either a char
or a double, so that I can write *p = 3 to set the char to
control-C or the double to 3.0?"
The answer is: there is no such pointer. You must change the
problem.

No, it is not. Your analogy is flawed for two reasons:

1. Both of the different data pointers in my question are pointers to
structures (gzFile, known externally as void, and FILE), although
different structures. Such pointers are interchangable.

2. I'm not trying to assign any values to the memory being pointed
to by these either (no dereferencing) -- just passing them down to
functions (gzgetc, fgetc), which know how to deal with them.

There is also another pair of pointers in my example -- pointers to
functions (gzgetc and fgetc). This confusion may have led you to the flawed
analogy, but, unlike pointers to _different_ data-types, pointers to
functions are interchangable too.

-mi
 
E

Eric Sosman

Mikhail said:
I'd like to have a variable of a pointer-to-function type. The two
possible values are of type (*)(FILE *) and (*)(void *). For example:

getter = straight ? fgetc : gzgetc;
[...]
nextchar = getter(file);

What type should I give to `getter' so that the compiler does not issue
warnings about an incompatible value getting assigned to it? Thanks!
There's no such type. What you're trying to do is not possible in
portable code. There's no guarantee that FILE * and void * are even the
same size let alone have the same representation, so you can't have a
single call that can invoke either one, you have to have separate calls
or write a wrapper function for one of the functions that has the same
type as the other function.


The standard definition of qsort() seems to disagree with you here. The
callbacks passed to qsort are expected to take `const void *' arguments and
cast them internally.

There is a difference between how qsort() works with its
comparison function and what you are attempting to do with a
kind of "one size fits all" function pointer.

When qsort() calls the comparison function, it passes two
`void*' arguments. The comparison function receives the `void*'
values and converts them to the desired pointer type. However,
qsort() does not pass an `int*' or a `struct foo*' or any such
thing, nor does the comparison function receive such a pointer:
qsort() passes a `void*' and the comparison function receives
a `void*', and that's that.
Just as these callbacks know the true type of their arguments, both fgetc
and gzgetc in my example know, what's passed to them -- and my program
knows, which pointer to use.

Function calls in C are "static" in the sense that the
number and types of all the arguments are known at compile
time and unchangeable thereafter. It is not possible to
write a call that sometimes passes a `void*' and sometimes
passes a `FILE*'.

The best you can do is to convert your `FILE*' to a `void*'
and pass it to a function that expects to receive a `void*'.
Your gzgetc() function can receive the `void*' directly, but
fgetc() cannot: it must receive a `FILE*', not a `void*' that
has been derived from the value of a `FILE*'. Hence Lawrence's
suggestion that you write a one-line wrapper that accepts the
`void*', converts it back to a `FILE*', and calls getc() with
the result:

int teterin_fgetc(void *fileptr) {
return getc((FILE*)fileptr);
}
As for pointers to different data-types being of different size, this is,
probably, even rarer than the (in)famous non-zero NULL pointer, is it not?

In English, this is called "begging the question." All
citizens are law-abiding, because lawbreakers are relatively
rare and shouldn't really be counted as citizens, right? All
crows are white, because any black crows I may happen to see
are probably ravens rather than true crows, okay? All pointers
are interchangeable, because I choose not to discuss systems
where they aren't; QED.

There are none so blind as those who will not see.
In my case, both functions (fgetc and gzgetc) expect a pointer to a
structure.

You described gzgetc() as taking a `void*', not a pointer
to a structure. Make up your mind ...

As for fgetc(), it most definitely does not expect or
accept a pointer to a structure; it requires a pointer to
a `FILE'. (No, don't post the contents of your system's
<stdio.h>; we're all aware of the sort of thing it probably
contains. Your <stdio.h> may well implement a `FILE*' as
a pointer to some kind of structure, but that doesn't imply
that every <stdio.h> does so. My system's <stdio.h> defines
_IOLBF as 64 and BUFSIZ as 1024; does that imply yours must
do the same?)
That said, a wrapper would, probably, be acceptable, as it will be optimized
away anyway :)

Maybe, maybe not. Do you think the difference would be
measurable?
 
C

Chris Torek

No, it is not. Your analogy is flawed for two reasons:

1. Both of the different data pointers in my question are pointers to
structures (gzFile, known externally as void, and FILE), although
different structures. Such pointers are interchangable.

No, not according to the original posting anyway. You said that
one function took a "void *" pointer, the other a "FILE *".

Now, go find yourself a Data General Eclipse machine, and compile
the following source code:

#include <stdio.h>

int get1(void *vp) {
FILE *fp = vp;
return fgetc(vp);
}

Observe the assembler-level code (either disassemble the object code,
or compile to assembly). Note that the assignment "fp = vp" *uses
a shift instruction*. The assembly code turns out to be identical
to that for:

int get1(unsigned int vp) {
unsigned int fp = vp >> 1;
return fgetc(fp);
}

Now compare that to the assembly code for:

int get2(void *vp) {
return gzgetc(vp);
}

Here there is no shift instruction.

In other words, assignments from "FILE *" to "void *" require shifts
(left or right depending on which is the source and which is the
destination), while assignments from "void *" to "void *" are simply
bit-for-bit copies. You *must* get the shift instruction, via the
wrapper, for the version that calls fgetc(), because fgetc() expects
its argument to be pre-shifted.
2. I'm not trying to assign any values to the memory being pointed
to by these either (no dereferencing) -- just passing them down to
functions (gzgetc, fgetc), which know how to deal with them.

The gzgetc() function takes a "void *", and the fgetc() function
takes a "FILE *" (which is a "struct ... *"). These pointers have
different formats. The insides of both gzgetc() and fgetc() assign
values to the data structures to which the pointers point. These
assignments are hidden, but do occur.
There is also another pair of pointers in my example -- pointers to
functions (gzgetc and fgetc). This confusion may have led you to the flawed
analogy, but, unlike pointers to _different_ data-types, pointers to
functions are interchangable too.

Pointers to functions are *interconvertible*. This is not, at
least with my definition of "interchangeable", quite the same as
interchangeable. Consider int and double: we can take an int value,
convert it to a double, and (on most machines) preserve *all* the
original values (because int is typically 32 or fewer bits, while
double has at least 53 bits of mantissa). If we then convert the
resulting double back to int, we get the original value back --
but the conversion has changed things internally. The internal
representations of (int)3 and (double)3.0 are quite different (again,
on most machines[%] anyway).
-----
% The Burroughs A-series stored integers in floating-point format.
I have some doubts whether anyone could build a working C
implementation on one of these, even if any are still out there
to use, but if group did build a C compiler for one, they might
use the same representation for int and double.
-----

The same holds, in principle, for function pointers in C. The
standard guarantees that, given any two function types FT1 and FT2
and function f() of type FT1, the result of:

(FT1)(FT2)f

is just as useful as the original pointer. The "round trip
conversion" from FT1 to FT2, and then back to FT1, via the two
casts, preserves the value. The standard does *not* guarantee that
the internal representations are the same. Unlike the Eclipse
example, I do not know of any machines where the conversion really
does change the representation -- but it is *allowed*, so portable
code should be sure to cause both conversions. On the Eclipse,
when dealing with data pointers, both conversions are required;
code that achieves only one of them -- including incorrect qsort()
functions, which were one of the canonical examples -- fails at
runtime.

Most modern machines have only one internal representation for
pointers (the generic byte pointer), which has led people to forget
all the "interesting" problems that occurred when porting code to
the Eclipse, or even some of the funny "memory models" of the older
x86 (only some of which were actually standard-conforming). The
C standards (C89 and C99 both) do allow for them, though, and with
the advent of 64-bit CPUs, I suspect some of them will make a
comeback for a while.
 
K

Keith Thompson

Mikhail Teterin said:
Mikhail Teterin said:
I'd like to have a variable of a pointer-to-function type. The two
possible values are of type (*)(FILE *) and (*)(void *). For example:

getter = straight ? fgetc : gzgetc;
[...]
nextchar = getter(file);

What type should I give to `getter' so that the compiler does not issue
warnings about an incompatible value getting assigned to it? Thanks!
There's no such type. What you're trying to do is not possible in
portable code. There's no guarantee that FILE * and void * are even the
same size let alone have the same representation, so you can't have a
single call that can invoke either one, you have to have separate calls
or write a wrapper function for one of the functions that has the same
type as the other function.

The standard definition of qsort() seems to disagree with you here. The
callbacks passed to qsort are expected to take `const void *' arguments and
cast them internally.

Yes, but if you call qsort() with a comparison function that takes
pointers to structs, you'll invoke undefined behavior.

Different pointer types can be converted to each other (in some cases)
*if* the compiler has enough information to do the conversion, i.e.,
if the compiler knows both the source and target pointer types. If
you hide this information from the compiler by using a single pointer
type to refer to two distinct function types, the compiler will
complain. If you then use casts to force the compiler to accept the
mistyped pointer, you're lying to the compiler, and it will get its
revenge by giving you undefined behavior.

As it happens, you're likely to get away with it on most modern
systems, since many of them use only a single internal representation
for all pointer types. Don't think of this permissiveness as the
implementation being friendly; think of it as the implementation being
actively hostile by failing to diagnose errors that will show up at
the most inconvenient possible moment, several years from now, when
you try to port the code to a less "friendly" implementation.

There's no direct way to do what you're trying to do, but there are a
number of indirect ways to do it. You can store a pointer to either
the fgetc or gzgetc function in a single variable of some
pointer-to-function type, but you have to convert it back to the
proper type before performing a call through the function. That means
you have to know what the proper type is. Rather than using just a
pointer, use a struct consisting of a function pointer and a flag of
some sort that tells you the actual type of the function you're
pointing to. C doesn't let you store types directly, but you could
use an enumeration and choose what to do based on its value. You can
then do something like:

typedef void (*func_ptr)(void);
typedef int (*fgetc_ptr)(FILE*);
typedef int (*gzgetc_ptr)(void*);

if (straight) {
getter.func = (func_ptr)fgetc;
getter.kind = 'f';
}
else {
getter.func = (func_ptr)gzgetc;
getter.kind = 'g';
}

...

nextchar = (getter.kind == 'f' ? ((fgetc_ptr)getter.func)(file)
: ((gzgetc_ptr)getter.func)(file));

Wrap some of this in macros if you like.

Of course, as long as you're selecting the type based on the "kind",
you probably might as well select the function that way and avoid the
confusion of storing a pointer to it. The best approach depends on
the design of your application.
 
R

Richard Bos

Mikhail Teterin said:
No, it is not. Your analogy is flawed for two reasons:

1. Both of the different data pointers in my question are pointers to
structures (gzFile, known externally as void, and FILE), although
different structures. Such pointers are interchangable.

You do not know that a FILE is a struct. It is

# an object type capable of recording all the information needed to
# control a stream ...

but that does not necessarily make it a struct. It probably usually is
one, but it isn't demanded by the Standard.

Richard
 

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,731
Messages
2,569,432
Members
44,832
Latest member
GlennSmall

Latest Threads

Top