arrays of function pointers impossible?

S

Seebs

Make that;
f[0] = (int (*)())strlen;
and
printf("%d\n", (int)((size_t (*)())f[0])("hello"));

and all is just fine.

Well yes, just about anything can be made to work if you lie to the
compiler.

I don't think that's lying to the compiler. The call to f is through
a suitably converted function pointer (though I think it ought to have
the full prototype, maybe?), and you can cast the result from that.

You're certainly allowed to cast a function pointer to another function
type and back.

-s
 
S

Shao Miller

Kenny said:
... ... ...
I could state that the sky is blue and the regs would pounce in to tell
me that I'm wrong.

In fact, let's count the responses from now until they do (i.e., until
one of them does).

I mightn't be a regular, but I think you're right, not wrong. If there
was a little more light at night-time, it'd probably show up as blue
then, too. (Just kidding around with you.) ;)

Just as a couple of references for what I've read in the last couple of
posts along this thread-branch, function pointer conversions have some
relevant text in 'n1256.pdf': Section 6.3.2.3, point 8. There is also
some relevant function compatibility detail beginning at section
6.7.5.2, point 15.
 
I

Ian Collins

Make that;
f[0] = (int (*)())strlen;
and
printf("%d\n", (int)((size_t (*)())f[0])("hello"));

and all is just fine.

Well yes, just about anything can be made to work if you lie to the
compiler.

I don't think that's lying to the compiler.

Most casts are lying to the compiler.
The call to f is through
a suitably converted function pointer (though I think it ought to have
the full prototype, maybe?), and you can cast the result from that.

It's still a bomb waiting to go off and a nightmare to debug.
You're certainly allowed to cast a function pointer to another function
type and back.

True, but if you know f[0] isn't what it is declared to be, why use an
array rather than a struct comprising the correct function pointer types?
 
T

Tim Streater

I could state that the sky is blue and the regs would pounce in to tell
me that I'm wrong.

Well you are wrong. I just looked out of the window, looks pretty black
to me (with some little bright dots in it, though).
 
S

Shao Miller

Ian said:
On 09/13/10 03:42 AM, Ralf Damaschke wrote:
Make that;
f[0] = (int (*)())strlen;
and
printf("%d\n", (int)((size_t (*)())f[0])("hello"));

and all is just fine.

Well yes, just about anything can be made to work if you lie to the
compiler.

I don't think that's lying to the compiler.

Most casts are lying to the compiler.

For objects, what is the "true" type? Is it declared type, effective
type, 'unsigned char', or something else?

I would agree that sometimes programmers might have
implementation-specific experience and resulting expectations that they
use which might be non-portable.

In this case, if we use the function pointer array element directly to
call a function with an non-compatible type, I think we'd be relying on
our beliefs about how the implementation is going to pass arguments and
deal with return values. That would seem a bit "dishonest", to me. :)
The call to f is through
a suitably converted function pointer (though I think it ought to have
the full prototype, maybe?), and you can cast the result from that.

It's still a bomb waiting to go off and a nightmare to debug.
You're certainly allowed to cast a function pointer to another function
type and back.

True, but if you know f[0] isn't what it is declared to be, why use an
array rather than a struct comprising the correct function pointer types?

Yeah. Or a 'union' of any function pointer type that might be useful,
if the function type is obvious from some other context.
 
I

Ian Collins

Well you are wrong. I just looked out of the window, looks pretty black
to me (with some little bright dots in it, though).

It should be blue here, but is has been "overcast" to grey!
 
S

Seebs

True, but if you know f[0] isn't what it is declared to be, why use an
array rather than a struct comprising the correct function pointer types?

Because the array is a generic data type which can be indexed.

Consider the following totally hypothetical* example:

static struct {
char *name; /* the name */
int (**real)(void); /* the underlying syscall */
int (*dummy)(void); /* the always-fails form */
int (*wrapper)(void); /* the wrapper from guts/name.c */
} pseudo_functions[] = {
{ /* int open(const char *path, int flags, ...); */
"open",
(int (**)(void)) &real_open,
(int (*)(void)) dummy_open,
(int (*)(void)) wrap_open
},
{ /* char * getcwd(char *buf, size_t size); */
"getcwd",
(int (**)(void)) &real_getcwd,
(int (*)(void)) dummy_getcwd,
(int (*)(void)) wrap_getcwd
},
[...]
{ NULL, NULL, NULL, NULL },
};

This table is usable despite the fact that virtually none of
the functions in it are actually functions taking void and returning
int. However, it can be used to have a single loop go over the
list processing the things in it appropriately... Consider also
a hypothetical table of operators for an interpreter, which might
declare a consistent function type, then cast functions before
calling them accordingly (untested, mighta typed this wrong):

switch (e->operands) {
case 1:
e = (expr *()(expr *))(e->fn)(e->operand1);
break;
case 2:
e = (expr *()(expr *, expr *))(e->fn)(e->operand1, e->operand2);
break;
}

While most trivial test programs have no real reason to cast function
pointers, many more interesting uses really do benefit hugely from being
able to have a single type which is used to hold pointers to many different
types of functions, which are then suitably cast before calling them.

-s
[*] Not hypothetical at all, and has never once been the cause of any
bugs out of millions of runs.
 
S

Seebs

Well you are wrong. I just looked out of the window, looks pretty black
to me (with some little bright dots in it, though).

Are you quite sure it's black, and not just a very, very, dark blue?

That said, only time I've recently seen someone saying Kenny was wrong,
Kenny was indeed saying something ludicrously stupid.

But seriously, Kenny. Just say something less stupid and you'll find
that people agree with you. I don't think I'd portray myself as a big
fan of John Kelly, but he's posted some eminently reasonable stuff over
in comp.unix.shell, and I've ended up arguing his side of things a
couple of times. It's got nothing to do with personality or status or
cliques; I just care whether what people say makes sense to me. I
mostly don't even know whose posts I'm reading, because my newsreader
tends to display article text in the whole window a lot of the time.
Fine by me. Posts stand or fall on their own merits.

But wait, we could test this! I propose a test to be run: I propose
that we accumulate a selection of frequent posters to clc, including
Kenny and Richard Nolastname, and that we then assemble answers to some
question from, say, ten different people, and post them all *anonymously*,
and see how people react to them. My guess is that you'll find that
the reactions don't differ substantially from what those people get when
posting under a recognizeable name. (Of course, people could easily cheat;
Kenny could try to write something lucid for variety, throwing off the
results.)

-s
 
I

Ian Collins

True, but if you know f[0] isn't what it is declared to be, why use an
array rather than a struct comprising the correct function pointer types?

Because the array is a generic data type which can be indexed.

Consider the following totally hypothetical* example:

static struct {
char *name; /* the name */
int (**real)(void); /* the underlying syscall */
int (*dummy)(void); /* the always-fails form */
int (*wrapper)(void); /* the wrapper from guts/name.c */
} pseudo_functions[] = {
{ /* int open(const char *path, int flags, ...); */
"open",
(int (**)(void))&real_open,
(int (*)(void)) dummy_open,
(int (*)(void)) wrap_open
},
{ /* char * getcwd(char *buf, size_t size); */
"getcwd",
(int (**)(void))&real_getcwd,
(int (*)(void)) dummy_getcwd,
(int (*)(void)) wrap_getcwd
},
[...]
{ NULL, NULL, NULL, NULL },
};

This table is usable despite the fact that virtually none of
the functions in it are actually functions taking void and returning
int. However, it can be used to have a single loop go over the
list processing the things in it appropriately...

True, but you have to know that pseudo_functions[0].real has a specific
signature to call it correctly. If someone were to change a function
signature, or the order in the array there would be no warnings from the
compiler (the cast tells it you know better) and potentially bizarre
run-time issues.
 
M

Marcin Grzegorczyk

Tim said:
Well you are wrong. I just looked out of the window, looks pretty black
to me (with some little bright dots in it, though).

Lucky you. I also looked out of the window and I could see only one
bright dot, the rest is drowned by city lights.
 
M

Marcin Grzegorczyk

Seebs said:
[...] Consider also
a hypothetical table of operators for an interpreter, which might
declare a consistent function type, then cast functions before
calling them accordingly (untested, mighta typed this wrong):

switch (e->operands) {
case 1:
e = (expr *()(expr *))(e->fn)(e->operand1);
break;
case 2:
e = (expr *()(expr *, expr *))(e->fn)(e->operand1, e->operand2);
break;
}

Frankly, in such case I'd rather use a union of all relevant function
pointer types, and use it like this:

case 1:
e = e->fn.a1(e->operand1);
break;
case 2:
e = e->fn.a2(e->operand1, e->operand2);
break;

Less casts means less opportunity to screw up :)
While most trivial test programs have no real reason to cast function
pointers, many more interesting uses really do benefit hugely from being
able to have a single type which is used to hold pointers to many different
types of functions, which are then suitably cast before calling them.

Agreed, there are cases where the union approach is impractical.
 
S

Seebs

True, but you have to know that pseudo_functions[0].real has a specific
signature to call it correctly. If someone were to change a function
signature, or the order in the array there would be no warnings from the
compiler (the cast tells it you know better) and potentially bizarre
run-time issues.

At which point we invoke the Spirit of C, which in the case of function
pointers and the risks of code which casts them, comes down to "Suck
it up, princess."

Which is to say: Yes, I totally agree. This kind of stuff is potentially
risky, certainly error-prone in some cases, and so on -- but is either
necessary or all-but necessary for a lot of the best uses of function
pointers in C. C doesn't really give you the introspection tools you'd
use to do this safely in some languages, so the solution is to be pretty
careful when maintaining such code.

I guess my big point of contention is that, as long as you actually do get
it right, you are NOT lying to the compiler. The problem here isn't that
you are lying to the compiler when you do it correctly -- it's that the
compiler has basically no way to TELL whether you are lying to it or not,
and thus, can't give you useful diagnostics in most cases.

Actually, I've seen cases where it could -- there was a hunk of code
in openssh which did some crazy stuff which ended up generating calls
that could be through the wrong type. At some point, gcc for POWER
got smart enough to realize that:

1. The optimizer sometimes crashed trying to deal with these calls.
2. These calls were undefined behavior.
3. The funniest thing to do would be to emit a (truthful) diagnostic
informing the user that the compiler had generated code to abort
execution if any of the calls in question were ever reached.

A bit surprising, maybe, but eminently useful. The bug got fixed
quickly as soon as gcc stopped being willing to work around it. :)

-s
 
S

Seebs

Frankly, in such case I'd rather use a union of all relevant function
pointer types, and use it like this:

That's actually an idea which had occurred to me, and I sort of like it.
Less casts means less opportunity to screw up :)

Yes.

-s
 
I

Ian Collins

True, but you have to know that pseudo_functions[0].real has a specific
signature to call it correctly. If someone were to change a function
signature, or the order in the array there would be no warnings from the
compiler (the cast tells it you know better) and potentially bizarre
run-time issues.

At which point we invoke the Spirit of C, which in the case of function
pointers and the risks of code which casts them, comes down to "Suck
it up, princess."

Fair enough!
Which is to say: Yes, I totally agree. This kind of stuff is potentially
risky, certainly error-prone in some cases, and so on -- but is either
necessary or all-but necessary for a lot of the best uses of function
pointers in C. C doesn't really give you the introspection tools you'd
use to do this safely in some languages, so the solution is to be pretty
careful when maintaining such code.

True, while I don't like it, I don't deny doing it...
I guess my big point of contention is that, as long as you actually do get
it right, you are NOT lying to the compiler. The problem here isn't that
you are lying to the compiler when you do it correctly -- it's that the
compiler has basically no way to TELL whether you are lying to it or not,
and thus, can't give you useful diagnostics in most cases.

It also defeats intelligent editing tools, from sed through to
refactoring editors. So yes, sometimes it it a necessary compromise,
but the code should be written in the digital equivalent of bright red ink.
 
L

luserXtrog

Ian said:
True, but if you know f[0] isn't what it is declared to be, why use an
array rather than a struct comprising the correct function pointer types?

Because the array is a generic data type which can be indexed.

Consider the following totally hypothetical* example:

static struct {
char *name; /* the name */
int (**real)(void); /* the underlying syscall */
int (*dummy)(void); /* the always-fails form */
int (*wrapper)(void); /* the wrapper from guts/name.c */
} pseudo_functions[] = {
{ /* int open(const char *path, int flags, ...); */
"open",
(int (**)(void))&real_open,
(int (*)(void)) dummy_open,
(int (*)(void)) wrap_open
},
{ /* char * getcwd(char *buf, size_t size); */
"getcwd",
(int (**)(void))&real_getcwd,
(int (*)(void)) dummy_getcwd,
(int (*)(void)) wrap_getcwd
},
[...]
{ NULL, NULL, NULL, NULL },
};

This table is usable despite the fact that virtually none of
the functions in it are actually functions taking void and returning
int. However, it can be used to have a single loop go over the
list processing the things in it appropriately...

True, but you have to know that pseudo_functions[0].real has a specific
signature to call it correctly. If someone were to change a function
signature, or the order in the array there would be no warnings from the
compiler (the cast tells it you know better) and potentially bizarre
run-time issues.

I've encountered this problem. The trick that worked for me was X-
macros.
This way I can keep the parallel data structures coordinated.

/* not tested -- top of head */

#define OPERATORS \
X(add, 2, 1) \
X(sub, 2, 1) \
X(mul, 2, 1) \
X(print, 1, 0) \
/* end OPERATORS */

struct oper {
void (*fp) (int *);
int argin;
int argout;
} optab[] = {
#define X(a,b,c) { a, b, c },
OPERATORS
#undef X
};

#define X(a,b,c) op_ ## a,
enum opcode { OPERATORS };
#undef X

#define X(a,b,c) #a,
char *opname[] = { OPERATORS };
#undef X

/* end illustration */

I believe this usage is consistent with the maxim Don't Repeat
Yourself.
But I'm too lazy to tell make all my dependencies, so I still have
problems
if I add new operators to the middle of the list.
 
J

John Bode

What did you try?  Post it!  Arrays of function pointers have been
around since the dark ages.

Here is some testcode.

main()
{
int (*f)[2]();
f[0]=strlen;
f[1]=strcmp;
printf("%d\n%d\n",(f[0])("hello"),(f[1])("abc","abd"));

The problem you're running into is that strlen() doesn't return int.
You're basically doing the equivalent of

int a[2];
a[0] = 1;
a[1] = (size_t) 2;

You can have arrays of function pointers, but all of the functions in
the array must have the same return type:

int foo() { return 1; }
int bar() { return 2; }
int bletch() { return 3; }
....
int main(void)
{
int (*f[3])() = {foo, bar, bletch};
...
}
 
B

Ben Bacarisse

John Bode said:
On Sep 10, 4:41 pm, parjit <[email protected]> wrote:
Here is some testcode.

main()
{
int (*f)[2]();
f[0]=strlen;
f[1]=strcmp;
printf("%d\n%d\n",(f[0])("hello"),(f[1])("abc","abd"));

The problem you're running into is that strlen() doesn't return int.
You're basically doing the equivalent of

int a[2];
a[0] = 1;
a[1] = (size_t) 2;

There is a significant difference, though. The OP's code violates a
constraint and requires a diagnostic whereas your example with size_t
does not. An example using object pointers might have been more
directly analogous.
You can have arrays of function pointers, but all of the functions in
the array must have the same return type:

That's not quite enough (as I am sure you know) but the simplification
won't help the OP unless their example was entirely spurious. Most
functions (and certainly strlen and strcmp) will be declared with a
prototype, so pointer compatibility will include the number and type of
the parameters.
int foo() { return 1; }
int bar() { return 2; }
int bletch() { return 3; }
...
int main(void)
{
int (*f[3])() = {foo, bar, bletch};
...
}

Using declarations (and in this case definitions) that are not also
prototypes hugely simplifies the syntax for function pointers but it
encourages an obsolete style that is not as type-safe as modern C. I
think it is best avoided.
 

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
474,083
Messages
2,570,591
Members
47,212
Latest member
RobynWiley

Latest Threads

Top