call function with inadequate args (via function pointer)

F

Felix Kater

Hi,

I can compile and run this code (see below) which twice calls the
function f, first with too less, second with too much arguments.

But is it legal and free of memory leaks and other problems? Of course,
I presume that inside f I don't access i in case it was called via g.

int f(int i){ /* ... */ return 0; }

int main(int argc, char** argv){

int(*g)(void)=f; /* less args than f */
int(*h)(int,int)=f; /* more args than f */

g();
h(0,1);

return 0;
}

Felix
 
W

Walter Roberson

Felix Kater said:
I can compile and run this code (see below) which twice calls the
function f, first with too less, second with too much arguments.
But is it legal and free of memory leaks and other problems? Of course,
I presume that inside f I don't access i in case it was called via g.
int f(int i){ /* ... */ return 0; }
int main(int argc, char** argv){
int(*g)(void)=f; /* less args than f */
int(*h)(int,int)=f; /* more args than f */
g();
h(0,1);
return 0;
}

If you have a call that does not match the number of parameters
given in a prototype, it is a constraint violation. If you
have a call that does not match the number of parameters when there
is no prototype, it is undefined behaviour.

But in a sense that's not what you are doing, in that you have
those explicit casts to a different function type. Therefore the
governing clause is,

"A pointer to a function of one type may be converted to a pointer
to a function of another type and back again; the result shall
compare equal to the original pointer. If a converted pointer
is used to call a function that has a type that is not compatible
with the type of the called function, the behavior is undefined."


A mismatch of the number of parameters is not "compatible" for the
above purposes.
 
S

santosh

Felix said:
Hi,

I can compile and run this code (see below) which twice calls the
function f, first with too less, second with too much arguments.

But is it legal and free of memory leaks and other problems? Of course,
I presume that inside f I don't access i in case it was called via g.

int f(int i){ /* ... */ return 0; }

int main(int argc, char** argv){

int(*g)(void)=f; /* less args than f */
int(*h)(int,int)=f; /* more args than f */

g();
h(0,1);

return 0;
}

No this causes undefined behaviour. The pointer declaration and the
function declaration must match.
 
F

Felix Kater

First, let me thank You for the details.

On Wed, 10 Jan 2007 18:40:57 +0000 (UTC)
If you have a call that does not match the number of parameters
given in a prototype, it is a constraint violation. If you
have a call that does not match the number of parameters when there
is no prototype, it is undefined behaviour.

But in a sense that's not what you are doing, in that you have
those explicit casts to a different function type. Therefore the
governing clause is,

"A pointer to a function of one type may be converted to a pointer
to a function of another type and back again; the result shall
compare equal to the original pointer. If a converted pointer
is used to call a function that has a type that is not compatible
with the type of the called function, the behavior is undefined."


A mismatch of the number of parameters is not "compatible" for the
above purposes.

So, I'd like to modify the example a bit: What would be in case of
using variable arguments like in these to cases (a + b):

(a)

/* added var args here: */
int f(int i,...){ /* ... */ return 0; }

int main(int argc, char** argv){

int(*g)(int,int)=f;
int(*h)(int,int,int)=f;

g(0,1);
h(0,1,2);

return 0;
}


(b)

int f1(int i){ /* ... */ return 0; }
int f2(int i, int k){ /* ... */ return 0; }

int main(int argc, char** argv){

/* one function pointer only...: */
int(*g)(int,...);

/* ...used in the first context: */
g=f1;
g(0); /* exact number of arguments? */
g(0,1); /* too many arguments? */

/* ...used in another context: */
g=f2;
g(0); /* not enough arguments? */
g(0,1,2); /* too many arguments? */

return 0;
}

Case (a) probably matches what is ment by "compatible" in your
explanation since direct calls to f with variable arguments are legal
as well.

However, what is with case (b)?

Felix
 
K

Keith Thompson

If you have a call that does not match the number of parameters
given in a prototype, it is a constraint violation. If you
have a call that does not match the number of parameters when there
is no prototype, it is undefined behaviour.

But in a sense that's not what you are doing, in that you have
those explicit casts to a different function type.
[...]

There are no casts in the posted code (and the phrase "explicit casts"
is redundant).

g is an object of type int(*)(void), and it's being initialized with a
value of type int(*)(int). With a cast, the initialization would be
legal, since any pointer-to-function type may be converted to any
other pointer-to-function type. But without the cast, it's a
constraint violation, since there is no *implicit* conversion for
pointer-to-function types (the constraints are the same as for simple
assignment).

gcc, for example, correctly prints diagnostic messages for these
constraint violations:

c.c: In function `main':
c.c:5: warning: initialization from incompatible pointer type
c.c:6: warning: initialization from incompatible pointer type

These are warnings, not error messages, but that's allowed; the
standard doesn't require the compiler to *reject* a translation unit
that contains constraint violations, merely to diagnose them. But if
the compiler does generate an executable after diagnosing a constraint
violation, the run-time behavior is undefined.

To the OP: If your compiler didn't at least give you a warning about
your code, the compiler is non-conforming; find out how to increase
its diagnostic level. If it did give you one or more warnings, you
shouldn't have ignored them (and you should have mentioned them in
your article).
 
K

Keith Thompson

Felix Kater said:
First, let me thank You for the details.
On Wed, 10 Jan 2007 18:40:57 +0000 (UTC)

As I mentioned in a previous response, there is no cast in the code,
and the initializations are constraint violations.
So, I'd like to modify the example a bit: What would be in case of
using variable arguments like in these to cases (a + b):

(a)

/* added var args here: */
int f(int i,...){ /* ... */ return 0; }

int main(int argc, char** argv){

int(*g)(int,int)=f;

Constraint violation. You're trying to initialize an object
of type int(*)(int,int) with a value of type int(*)(int,...).
int(*h)(int,int,int)=f;

Likewise, this is a constraint violation.
g(0,1);
h(0,1,2);

return 0;
}


(b)

int f1(int i){ /* ... */ return 0; }
int f2(int i, int k){ /* ... */ return 0; }

int main(int argc, char** argv){

/* one function pointer only...: */
int(*g)(int,...);

/* ...used in the first context: */
g=f1;

Constraint violation.
g(0); /* exact number of arguments? */
g(0,1); /* too many arguments? */

/* ...used in another context: */
g=f2;

Constraint violation.
g(0); /* not enough arguments? */
g(0,1,2); /* too many arguments? */

return 0;
}
[snip]

Consider what would happen if this were allowed. Variadic functions
may have an entirely different calling convention from non-variadic
functions, even if the number and type of arguments for a particular
call happen to be consistent. To generate correct code for a call,
the compiler must know the function's calling convention at the point
of the call. Allowing implicit conversions between
pointer-to-function types would create cases of undefined behavior
whenever the function types don't match. Requiring an explicit
conversion (a cast) to accomplish this places the burden on the
programmer to make sure the types match, or to (somehow) determine
that the call will work anyway even if they don't.

Consider this program:

int printf(char *format, int arg);

int main(void)
{
int x = 42;
printf("x = %d\n", x);
return 0;
}

Note that there is no "#include <stdio.h>", and the prototype for
printf is inconsistent it's actual definition. The call to printf is
compatible with the given declaration, and would be legal if a correct
declaration were visible, but it invokes undefined behavior.

Q: Why does it invoke undefined behavior?
A: Because the standard says so.

Q: Why does the standard say so (i.e., what is the rationale)?
A: To allow implementations to use different calling conventions for
variadic and non-variadic functions. For example, a compiler might
pass arguments in registers for a non-variadic function, but on a
stack for variadic functions. (But many implementations use
consistent conventions anyway, to avoid breaking pre-ANSI code
written when prototypes weren't available.)

The above program works "correctly" on one implementation where I
tried it, but the compiler was kind enough to issue a (non-required)
diagnostic:

c.c:1: warning: conflicting types for built-in function 'printf'
 
F

Felix Kater

Let me thank You for the helpful details!

As I mentioned in a previous response, there is no cast in the code,
and the initializations are constraint violations.

Ok, I see.

[...]
Consider what would happen if this were allowed. Variadic functions
may have an entirely different calling convention from non-variadic
functions, even if the number and type of arguments for a particular
call happen to be consistent.

This was a helpful hint, thanks.

[...]
To generate correct code for a call,
the compiler must know the function's calling convention at the point
of the call. Allowing implicit conversions between
pointer-to-function types would create cases of undefined behavior
whenever the function types don't match. Requiring an explicit
conversion (a cast) to accomplish this places the burden on the
programmer to make sure the types match, or to (somehow) determine
that the call will work anyway even if they don't.

I wonder if I really could achieve what I need (calling different
functions via the same function pointer) by simply inserting a cast like
this -- which would be fantastic:

int f(int i, int k){ /* ... */ return 0; }

int main(int argc, char** argv){

int(*g)(int,...);

g=((*)(int,...))f;

g(0); /* not enough arguments? */
g(0,1,2); /* too many arguments? */

return 0;
}

First, with this cast there is no warning from gcc anymore as expected.

Second, I wonder, if this cast is performed into the right direction
(a) and really helps (b):

(a) Of course, this cast is towards my purpose since I'd like to call
different functions f1, f2, f3 via a single function pointer g -- rather
than casting g into f1 (f2, f3, ...) which I can't do since in my case I
don't have the prototypes of f1, f2, f3.

(b) However, does this cast make the two calls of g safe(r) ? What does
a conforming compiler do with the two calls of g? Does it help behind
the scenes and add or discard arguments (when there are more or less
than f needs) so that there are no memory leaks etc.? This is what I
aim to achieve. -- I would like to point out that it is presumed (and in
my case ok) that inside f the passed arguments may not contain reliable
values.

Felix
 
W

Walter Roberson

Felix Kater said:
I wonder if I really could achieve what I need (calling different
functions via the same function pointer) by simply inserting a cast like
this -- which would be fantastic:
int f(int i, int k){ /* ... */ return 0; }
int main(int argc, char** argv){

int(*g)(int,...);

g=((*)(int,...))f;

g(0); /* not enough arguments? */
g(0,1,2); /* too many arguments? */

return 0;
}

No, because this has the opposite problem to before. Now g will be called
with varadic semantics, but f is expecting non-varadic semantics.
There are compilers for which this will make a crucial difference;
see for example the ABI details for SGI IRIX MipsPro compilers
at techpubs.sgi.com .

If you need to be able to call a function with different number
of arguments, just declare it varadic in the first place, and then
use it varadic in your code. And unless you have good reason
otherwise, just stick with the original varadic function name --
after all, when you get into f(), f() is not going to be able to
tell which of its aliases it was called by.
 
K

Keith Thompson

Felix Kater said:
I wonder if I really could achieve what I need (calling different
functions via the same function pointer) by simply inserting a cast like
this -- which would be fantastic:
[snip]

No, that still invokes undefined behavior. The cast, as casts often
do, act as a promise to the compiler that you really know what you're
doing, and it shouldn't bother you with warning messages. In this
case, you're lying to the compiler.

On many systems, the calling conventions are consistent enough that
you can get away with this kind of thing. In pre-ANSI (K&R) C, there
were no prototypes; the only declaration you might have for printf(),
for example, is:

int printf();

Since the compiler had no idea when it saw a call that printf() is
variadic, or even that it expects any arguments at all, it would have
to generate code for the call based purely on the actual arguments.
Since this was the case for all function calls, all functions had to
have the same calling conventions.

The introduction of prototypes in ANSI C89 made it possible for the
compiler to know when it sees a call exactly what the function
expects, and therefore allowed for more sophisticated calling
conventions. The generated code for printf("%d", n) and
some_other_func("%d", n) might be very different, based on the
functions' prototypes -- which is why calling printf() with no visible
prototype to provide that information to the compiler invokes
undefined behavior.

But some implementations have kept the older calling conventions to
avoid breaking older code, or just out of inertia (there might not be
any reason to change the conventions if the older ones worked well
enough).

Which means that if you make the mistake of calling a function without
properly telling the compiler about the function's prototype, (a)
you'll most likely invoke undefined behavior, and (b) it will *appear*
to work on many implementations, so you won't detect the error until
the most embarrassing possible moment. So don't do that.

Here's what the standard says (C99 6.3.2.3p8):

A pointer to a function of one type may be converted to a pointer
to a function of another type and back again; the result shall
compare equal to the original pointer. If a converted pointer is
used to call a function whose type is not compatible with the
pointed-to type, the behavior is undefined

So you can use a single "generic" pointer-to-function type to store
pointers to arbitrary functions, but you must keep track of the actual
type of each function and explicitly convert the pointer back to the
proper type before calling it through the pointer. Which means you
can only have a fixed number of function types that you can handle
this way -- but you're only going to have a fixed number of functions
in your program anyway. (There's no defined generic
pointer-to-function type, like void* for pointer-to-object types, but
you can pick one arbitrarily, like void(*)(void).)
 
F

Felix Kater

On Thu, 11 Jan 2007 17:05:17 +0000 (UTC)
If you need to be able to call a function with different number
of arguments, just declare it varadic in the first place, and then
use it varadic in your code.

This, of course, is a way.

What a pitty, though. It seems there is no way to lift the burden from
the other programmers to deal with va_lists that I provide.

Thanks a lot.

Felix
 
F

Felix Kater

Which means you
can only have a fixed number of function types that you can handle
this way -- but you're only going to have a fixed number of functions
in your program anyway.

That's exactly the point! In my case we *do* have something like a
variable number of functions: In a running main app (which is my part)
it is planned to rewrite and recompile a set of other libraries, which
in turn are reloaded again dynamically. After reloading the libraries
register their public functions dynamically with a function pointer
list, hold by the main app.

Now, as all function calls from the libraries are narrowed through the
main app, we can get control over runlevels, access rights etc.

Coming back to the above problems of my original posting: As the
programmers of the libraries (a) should know their function's arguments
and (b) all calls are passed back to their own libraries again by the
main app, the problem of inadequate arguments is up the library
programmers.

The main app does not need to know the prototypes of the libraries
functions.

However, as it seems to me now, narrowing all calles though the main
app's function pointer lists means there is no way to use fixed
arguments (but varadic only) for the libraries functions. This makes
things a more complicated for the library programmers. They have to
pack in and out their arguments via va_lists -- somthing I wanted
to avoid.

Thanks a lot for the detailed explanations.

Felix
 
M

Martin Golding

In my case we *do* have something like a
variable number of functions: In a running main app (which is my part) it
is planned to rewrite and recompile a set of other libraries, which in
turn are reloaded again dynamically. After reloading the libraries
register their public functions dynamically with a function pointer list,
hold by the main app.
Now, as all function calls from the libraries are narrowed through the
main app, we can get control over runlevels, access rights etc.
Coming back to the above problems of my original posting: As the
programmers of the libraries (a) should know their function's arguments
and (b) all calls are passed back to their own libraries again by the main
app, the problem of inadequate arguments is up the library programmers.
The main app does not need to know the prototypes of the libraries
functions.
However, as it seems to me now, narrowing all calles though the main app's
function pointer lists means there is no way to use fixed arguments (but
varadic only) for the libraries functions. This makes things a more
complicated for the library programmers. They have to pack in and out
their arguments via va_lists -- somthing I wanted to avoid.

That's probably wrong. The pointer must be cast to the right type when
invoked. When being passed or stored, it can be cast to a pointer to
any function type. Since the functions must be passed the correct
parameters when invoked, they must be invoked by a process that knows
what those parameters are, and can cast the pointer to the correct type.

Martin
 

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,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top