call function with inadequate args (via function pointer)

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Felix Kater

    call function with inadequate args (via function pointer)

    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
  • Walter Roberson

    #2
    Re: call function with inadequate args (via function pointer)

    In article <20070110184904 .e56da69b.fkate r@googlemail.co m>,
    Felix Kater <fkater@googlem ail.comwrote:
    >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.
    --
    "It is important to remember that when it comes to law, computers
    never make copies, only human beings make copies. Computers are given
    commands, not permission. Only people can be given permission."
    -- Brad Templeton

    Comment

    • santosh

      #3
      Re: call function with inadequate args (via function pointer)

      Felix Kater wrote:
      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.

      Comment

      • Felix Kater

        #4
        Re: call function with inadequate args (via function pointer)

        First, let me thank You for the details.

        On Wed, 10 Jan 2007 18:40:57 +0000 (UTC)
        roberson@ibd.nr c-cnrc.gc.ca (Walter Roberson) wrote:
        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

        Comment

        • Keith Thompson

          #5
          Re: call function with inadequate args (via function pointer)

          roberson@ibd.nr c-cnrc.gc.ca (Walter Roberson) writes:
          In article <20070110184904 .e56da69b.fkate r@googlemail.co m>,
          Felix Kater <fkater@googlem ail.comwrote:
          >
          >>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.
          [...]

          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).

          --
          Keith Thompson (The_Other_Keit h) kst-u@mib.org <http://www.ghoti.net/~kst>
          San Diego Supercomputer Center <* <http://users.sdsc.edu/~kst>
          We must do something. This is something. Therefore, we must do this.

          Comment

          • Keith Thompson

            #6
            Re: call function with inadequate args (via function pointer)

            Felix Kater <fkater@googlem ail.comwrites:
            First, let me thank You for the details.
            On Wed, 10 Jan 2007 18:40:57 +0000 (UTC)
            roberson@ibd.nr c-cnrc.gc.ca (Walter Roberson) wrote:
            >
            >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."
            As I mentioned in a previous response, there is no cast in the code,
            and the initializations are constraint violations.
            >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;
            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'

            --
            Keith Thompson (The_Other_Keit h) kst-u@mib.org <http://www.ghoti.net/~kst>
            San Diego Supercomputer Center <* <http://users.sdsc.edu/~kst>
            We must do something. This is something. Therefore, we must do this.

            Comment

            • Felix Kater

              #7
              Re: call function with inadequate args (via function pointer)

              Let me thank You for the helpful details!

              On Wed, 10 Jan 2007 14:42:55 -0800
              Keith Thompson <kst-u@mib.orgwrote:
              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


              Comment

              • Walter Roberson

                #8
                Re: call function with inadequate args (via function pointer)

                In article <20070111132240 .41c8a370.fkate r@googlemail.co m>,
                Felix Kater <fkater@googlem ail.comwrote:
                >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.co m .

                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.
                --
                "It is important to remember that when it comes to law, computers
                never make copies, only human beings make copies. Computers are given
                commands, not permission. Only people can be given permission."
                -- Brad Templeton

                Comment

                • Keith Thompson

                  #9
                  Re: call function with inadequate args (via function pointer)

                  Felix Kater <fkater@googlem ail.comwrites:
                  [...]
                  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).)

                  --
                  Keith Thompson (The_Other_Keit h) kst-u@mib.org <http://www.ghoti.net/~kst>
                  San Diego Supercomputer Center <* <http://users.sdsc.edu/~kst>
                  We must do something. This is something. Therefore, we must do this.

                  Comment

                  • Felix Kater

                    #10
                    Re: call function with inadequate args (via function pointer)

                    On Thu, 11 Jan 2007 17:05:17 +0000 (UTC)
                    roberson@ibd.nr c-cnrc.gc.ca (Walter Roberson) wrote:
                    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

                    Comment

                    • Felix Kater

                      #11
                      Re: call function with inadequate args (via function pointer)

                      On Thu, 11 Jan 2007 13:04:47 -0800
                      Keith Thompson <kst-u@mib.orgwrote:
                      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

                      Comment

                      • Martin Golding

                        #12
                        Re: call function with inadequate args (via function pointer)

                        On Fri, 12 Jan 2007 10:00:29 +0100, Felix Kater wrote:
                        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
                        --
                        Martin Golding DoD #0236 | fogobum@comcast .net
                        Always code as if the person who ends up maintaining your code will be a
                        violent psychopath who knows where you live.

                        Comment

                        Working...