function called through a non-compatible type

Discussion in 'C Programming' started by christer-sandberg, May 13, 2005.

  1. When I typecast a function and call it trough the casted pointer (se
    code below) I get the warnings:
    warning: function called through a non-compatible type
    if this code is reached, the program will abort
    and the gcc generates abort code for the call. This happens when I use
    gcc 3.4 but not when I use 3.3. Is there any flag that can change this
    behavior?
    I am aware that ANSI-99 says (in 6.3.2.3-8): "If a converted pointer is
    used to call a function whose type is not compatible with the
    pointed-to type, the behaviour is undefined". But I can't see how the
    pointers are incompatible.
    int foo(void *x) {
    return *(int*)x;
    }
    int main(void) {
    int x=0;
    return ((int(*)(int*))foo)(&x);
    }
    This kind of typecasting can sometimes be very useful in macros to
    ensure correct types of call-back functions (e.g such a macro can be
    used to match the data-pointer to qsort with the arguments to the
    parameter types of the call-back-function).
    --
    Christer
     
    christer-sandberg, May 13, 2005
    #1
    1. Advertising

  2. christer-sandberg

    Eric Sosman Guest

    christer-sandberg wrote:
    > When I typecast a function and call it trough the casted pointer (se
    > code below) I get the warnings:
    > warning: function called through a non-compatible type
    > if this code is reached, the program will abort
    > and the gcc generates abort code for the call. This happens when I use
    > gcc 3.4 but not when I use 3.3. Is there any flag that can change this
    > behavior?


    Ask on a gcc newsgroup. Suggested subject: "How can
    I disable my airbags, seat belts, and brakes?"

    > I am aware that ANSI-99 says (in 6.3.2.3-8): "If a converted pointer is
    > used to call a function whose type is not compatible with the
    > pointed-to type, the behaviour is undefined". But I can't see how the
    > pointers are incompatible.
    > int foo(void *x) {
    > return *(int*)x;
    > }
    > int main(void) {
    > int x=0;
    > return ((int(*)(int*))foo)(&x);
    > }


    The argument to foo() is a `void*'. The cast specifies
    an `int*' argument. `void*' and `int*' are incompatible,
    hence foo() and the converted pointer are incompatible.

    > This kind of typecasting can sometimes be very useful in macros to
    > ensure correct types of call-back functions (e.g such a macro can be
    > used to match the data-pointer to qsort with the arguments to the
    > parameter types of the call-back-function).


    I'm not entirely certain what you mean here, but if
    you mean what I think you mean then I think you're wrong.

    What I think you mean is that you would like to write
    casts to convert a pointer to J. Random Function to the type
    expected by, say, qsort():

    int f(int *x, int *y) { ... }
    ...
    qsort(..., (int (*)(const void*, const void*))f);

    This does not "ensure correct types of call-back functions,"
    not at all. It merely attempts to disguise a function of
    the Wrong type as a function of the Right type so qsort()
    will accept it. If the disguise succeeds qsort() may accept
    the function -- but there's just no telling what may happen
    when qsort() tries to call it.

    Here's another one you might try as a thought experiment:

    qsort(..., (int (*)(const void*, const void*))free);

    Do you think that the cast will somehow magically transform
    free() into a well-behaved comparison function? If not, why
    would you expect it to do so with f(), above?

    --
     
    Eric Sosman, May 13, 2005
    #2
    1. Advertising

  3. "christer-sandberg" <> writes:
    > When I typecast a function and call it trough the casted pointer (se
    > code below) I get the warnings:
    > warning: function called through a non-compatible type
    > if this code is reached, the program will abort
    > and the gcc generates abort code for the call. This happens when I use
    > gcc 3.4 but not when I use 3.3. Is there any flag that can change this
    > behavior?
    > I am aware that ANSI-99 says (in 6.3.2.3-8): "If a converted pointer is
    > used to call a function whose type is not compatible with the
    > pointed-to type, the behaviour is undefined". But I can't see how the
    > pointers are incompatible.
    > int foo(void *x) {
    > return *(int*)x;
    > }
    > int main(void) {
    > int x=0;
    > return ((int(*)(int*))foo)(&x);
    > }


    The pointers are incompatible because they point to two different
    function types. In particular, one function takes a void* argument,
    and the other takes an int* argument. On some implementations, it's
    entirely possible that void* and int* have different representations.
    Calling a function that expects a void* through a pointer to a
    function that expects an int* invokes undefined behavior.

    > This kind of typecasting can sometimes be very useful in macros to
    > ensure correct types of call-back functions (e.g such a macro can be
    > used to match the data-pointer to qsort with the arguments to the
    > parameter types of the call-back-function).


    That's a bad idea; it can blow up in your face when the code is ported
    to another implementation.

    On the other hand, gcc's behavior of deliberately inserting code to
    abort the program does seem a little odd. As far as I know, void* and
    int* have the same representation on all platforms that gcc supports
    (though I could be mistaken), so the call would probably work if gcc
    allowed it. But your best bet is to avoid calling functions through
    incompatible pointer types.

    --
    Keith Thompson (The_Other_Keith) <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.
     
    Keith Thompson, May 13, 2005
    #3
  4. Thanks for your answer.
    Eric Sosman wrote:
    > The argument to foo() is a `void*'. The cast specifies
    > an `int*' argument. `void*' and `int*' are incompatible,

    If I don't remember wrong, according to ANSI-C from 99 a void pointer
    can be assigned the value of a pointer to an object and they should
    compare equal. In what sense do you mean they should be incompatible?


    > > This kind of typecasting can sometimes be very useful in macros to
    > > ensure correct types of call-back functions (e.g such a macro can

    be
    > > used to match the data-pointer to qsort with the arguments to the
    > > parameter types of the call-back-function).

    >
    > I'm not entirely certain what you mean here, but if
    > you mean what I think you mean then I think you're wrong.
    >
    > What I think you mean is that you would like to write
    > casts to convert a pointer to J. Random Function to the type
    > expected by, say, qsort():
    >
    > int f(int *x, int *y) { ... }
    > ...
    > qsort(..., (int (*)(const void*, const void*))f);
    >
    > This does not "ensure correct types of call-back functions,"
    > not at all. It merely attempts to disguise a function of
    > the Wrong type as a function of the Right type so qsort()
    > will accept it. If the disguise succeeds qsort() may accept
    > the function -- but there's just no telling what may happen
    > when qsort() tries to call it.
    >
    > Here's another one you might try as a thought experiment:
    >
    > qsort(..., (int (*)(const void*, const void*))free);
    >
    > Do you think that the cast will somehow magically transform
    > free() into a well-behaved comparison function? If not, why
    > would you expect it to do so with f(), above?

    I meant niether of them. Rather something like what I wrote (sorry for
    my bad English)
    a macro ensuring the correct type of parameters to the callback
    (otherwise the the parameters needs to be casted in the function
    definition in which case there is nothing ensuring that the type of the
    array is used) e.g.:
    #define QSORT(d, n, t, f) (void(*)(t*, size_t, size_t, int(*)(const t*,
    const t*))) \
    qsort(d, n, sizeof(t), f);
    I can be done some ather ways too, but as I see it, it is an advantage
    to be able to force the type of array, the size of the array elements
    and the types of the paramaters to the call.back be the same.
    --
    Christer
     
    christer-sandberg, May 13, 2005
    #4
  5. "christer-sandberg" <> writes:
    > Thanks for your answer.
    > Eric Sosman wrote:
    >> The argument to foo() is a `void*'. The cast specifies
    >> an `int*' argument. `void*' and `int*' are incompatible,

    > If I don't remember wrong, according to ANSI-C from 99 a void pointer
    > can be assigned the value of a pointer to an object and they should
    > compare equal. In what sense do you mean they should be incompatible?


    Yes, they can be assigned. In an assignment, the compiler knows
    whether it needs allow for a change of representation to implement the
    conversion. If you use a function pointer of the wrong type, the
    called function has no way of knowing that the parameter isn't of the
    expected type, so it won't allow for any change of representation.
    You're not converting the pointer, you're pretending that it's of a
    different type.

    --
    Keith Thompson (The_Other_Keith) <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.
     
    Keith Thompson, May 13, 2005
    #5
  6. Thanks for your answer
    Keith Thompson wrote:
    > On some implementations, it's
    > entirely possible that void* and int* have different representations.

    That seems to be a reason (actually I suspected something like that). I
    guess it could be something like different pointer types can be of
    different sizes and a void pointer must be big enough to contain any
    othe pointer type, and in case int* is smaller there may be undefined
    data in the gap. This requires howeveer the difference of the sizes to
    be at least so big that the compiler can save som space (rather that
    just need to 0-extend the small ones). Do you happen to know if there
    are such cases?

    > Calling a function that expects a void* through a pointer to a
    > function that expects an int* invokes undefined behavior.

    According the rest of what you write, shouldn't it be ".. may on some
    platforms invoke..." here?

    > On the other hand, gcc's behavior of deliberately inserting code to
    > abort the program does seem a little odd. As far as I know, void*

    and
    > int* have the same representation on all platforms that gcc supports
    > (though I could be mistaken), so the call would probably work if gcc
    > allowed it.

    Yes, e.g. the below code is both legal and executable (on my target):
    static int foo(void*x) {
    return *(int*)x;
    }
    int main(void) {
    int x=0;
    return ((int (*)(int*))((void*)foo))(&x);
    }
    --
    Christer
     
    christer-sandberg, May 13, 2005
    #6
  7. christer-sandberg

    Eric Sosman Guest

    christer-sandberg wrote:
    > Thanks for your answer.
    > Eric Sosman wrote:
    >
    >> The argument to foo() is a `void*'. The cast specifies
    >>an `int*' argument. `void*' and `int*' are incompatible,

    >
    > If I don't remember wrong, according to ANSI-C from 99 a void pointer
    > can be assigned the value of a pointer to an object and they should
    > compare equal. In what sense do you mean they should be incompatible?


    The Standardese answer is "In the sense of 6.2.7, where
    type compatibility is defined."

    A (possibly) more helpful answer is by example. You can
    convert any `int*' to a `void*' and back again, but it need
    not be true that you can do the reverse: there may be `void*'
    values that cannot be converted to `int*' successfully. On
    many or even most present-day systems, such conversions can
    produce values that are not usable as `int*'. So it's clear
    that `int*' and `void*' are not the same type, even though
    there are some pointer values that both can represent.

    Or here's another: On every system I have ever encountered
    (though I don't believe the Standard actually requires it),
    every `char' value can be converted to `double' and back, and
    the result compares equal to the original. Does this mean that
    `char' and `double' are compatible types? Of course not.

    > [...]
    > I meant niether of them. Rather something like what I wrote (sorry for
    > my bad English)
    > a macro ensuring the correct type of parameters to the callback
    > (otherwise the the parameters needs to be casted in the function
    > definition in which case there is nothing ensuring that the type of the
    > array is used) e.g.:
    > #define QSORT(d, n, t, f) (void(*)(t*, size_t, size_t, int(*)(const t*,
    > const t*))) \
    > qsort(d, n, sizeof(t), f);
    > I can be done some ather ways too, but as I see it, it is an advantage
    > to be able to force the type of array, the size of the array elements
    > and the types of the paramaters to the call.back be the same.


    Ingenious -- but even worse than what I thought you were
    attempting! I'd supposed you were trying to disguise `f' so
    the compiler wouldn't complain when you passed it to qsort().
    Instead, you're lying about the nature of qsort() itself:

    - You're telling the compiler to pass the first argument
    as a `t*', but qsort() expects to receive a `void*'.
    If any conversion from `t*' to `void*' is needed, your
    recipe will prevent it from happening; if `t*' and `void*'
    are passed to functions differently (e.g. in different
    registers), your recipe will put the first argument in
    the wrong place.

    - You're telling the compiler that the fourth argument is
    a pointer to a function taking two `const t*' arguments,
    and your recipe will cause the compiler to complain if
    the actual `f' is different. But the real qsort() doesn't
    want such an `f'; it wants an `f' that takes a pair of
    `const void*'. If there's any difference between these
    (e.g., if a function pointer isn't a mere address but
    also carries something like a "dope vector"), you cannot
    expect this to work.

    - And despite all these shenanigans, qsort() is still going
    to pass `void*' arguments -- not `t*' arguments -- when it
    calls `f'. Since that's not what `f' expects to receive,
    there's no telling what might happen.

    I agree that gcc's insertion of abort() is a bit draconian,
    but it's understandable: gcc is trying to catch "really bad
    incompatibilities," but it can only discern "incompatibility;"
    it seems to have no notion of "probably venial incompatibility."
    And you have to admit that it's had at least one good effect:
    it's brought you here to learn the error of your (ingenious
    albeit misguided) ways!

    Enforcement of matching types is a weak point of C: there
    are a number of places where you must rely on vigilance and can
    expect little if any help from the language or the compiler.
    "Type-blind" functions like qsort() and bsearch() -- memcpy(),
    for that matter -- use `void*' precisely because they want to
    be able to operate on any kind of data object, but blindness
    has its drawbacks. Type-checking is defeated (that's the
    purpose, after all), but that also means you lose the benefits
    of type-checking. Your effort to restore those benefits is
    laudable, but I think it's doomed. You might go as far as

    #define QSORT(a, n, f) \
    qsort((a), (n), sizeof *(a), (f))

    .... to ensure that the third argument is correct, but I can
    think of no way to ensure that `f' is the right function to use
    with the type of data that happens to be in `a'. (There's even
    less hope that you could ensure that `f' imposes a total ordering
    on the values of that type!) Some constraints of a function's
    documentation are simply not expressible in the language; C
    shares this problem with other languages, too.

    "If you lie to the compiler [even with the best of
    intentions], it will get its revenge."

    --
     
    Eric Sosman, May 13, 2005
    #7
  8. "christer-sandberg" <> writes:
    > Thanks for your answer
    > Keith Thompson wrote:
    >> On some implementations, it's
    >> entirely possible that void* and int* have different representations.

    > That seems to be a reason (actually I suspected something like that). I
    > guess it could be something like different pointer types can be of
    > different sizes and a void pointer must be big enough to contain any
    > othe pointer type, and in case int* is smaller there may be undefined
    > data in the gap. This requires howeveer the difference of the sizes to
    > be at least so big that the compiler can save som space (rather that
    > just need to 0-extend the small ones). Do you happen to know if there
    > are such cases?


    I suspect the AS/400 is such a case; it seems to be the canonical
    example of an exotic platform that has a conforming C implementation
    while violating everyone's naive expectations.

    Cray vector machines use a different representation for char* and
    void* pointers than for int* pointers. A native machine address
    points to a 64-bit word; byte pointers (implemented in software) store
    a 3-bit offset in the unused high-order bits. The representation is
    such that an arbitrary int* pointer happens to be a valid byte pointer
    to the first byte of the word (the offset happens to be zero).

    In any case, differences in size aren't the only way that two pointer
    types can have a different representation.

    >> Calling a function that expects a void* through a pointer to a
    >> function that expects an int* invokes undefined behavior.

    > According the rest of what you write, shouldn't it be ".. may on some
    > platforms invoke..." here?


    No, it invokes undefined behavior on all systems. On some systems,
    the UB shows up as the program doing exactly what you expect it to do
    (until you upgrade your compiler, or demonstrate your program to an
    important customer, or maybe forever). That's what "undefined" means.

    >> On the other hand, gcc's behavior of deliberately inserting code to
    >> abort the program does seem a little odd. As far as I know, void*

    > and
    >> int* have the same representation on all platforms that gcc supports
    >> (though I could be mistaken), so the call would probably work if gcc
    >> allowed it.

    > Yes, e.g. the below code is both legal and executable (on my target):
    > static int foo(void*x) {
    > return *(int*)x;
    > }
    > int main(void) {
    > int x=0;
    > return ((int (*)(int*))((void*)foo))(&x);
    > }


    What do you mean by "legal"? It invokes undefined behavior. In fact,
    I just noticed that you're casting the value of foo (a
    pointer-to-function) to void*. That's not just undefined behavior,
    it's a constraint violation; a conforming compiler is required to
    diagnose it.

    If your compiler happens to allow it, and happens to do what you
    expect, that doesn't make it legal.

    --
    Keith Thompson (The_Other_Keith) <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.
     
    Keith Thompson, May 13, 2005
    #8
  9. christer-sandberg

    Guest

    Although I don't know how gcc dereferences a void *, on some
    implementations I think dereferencing a void * gives you one byte
    rather than a 32-bit word. This would cause irregular behaviour if you
    made such a function argument cast, as functions tend to have code
    specific to the range of types in the arguments. (e.g. a case that
    overflows a char does not overflow an int) I think this comes down to
    overloading, and thats also why overloaded functions would have
    distinct code.

    I think insertion of abort code comes from the distinction between
    overloading (which would generate different code) and the simple case
    of casting between two data types. the compiler can handle the latter
    by simply dereferencing a different portion of memory.

    Bahadir
     
    , May 14, 2005
    #9
  10. writes:
    > Although I don't know how gcc dereferences a void *, on some
    > implementations I think dereferencing a void * gives you one byte
    > rather than a 32-bit word.


    Deferencing a void* is illegal. You have to convert it to a pointer
    to some object type before you can dereference it.

    > This would cause irregular behaviour if you
    > made such a function argument cast, as functions tend to have code
    > specific to the range of types in the arguments. (e.g. a case that
    > overflows a char does not overflow an int) I think this comes down to
    > overloading, and thats also why overloaded functions would have
    > distinct code.


    C doesn't have overloaded functions (except for the new type-generic
    math stuff in C99, but that's a special case).

    --
    Keith Thompson (The_Other_Keith) <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.
     
    Keith Thompson, May 14, 2005
    #10
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. =?Utf-8?B?TSBL?=
    Replies:
    1
    Views:
    21,867
    AbhijitSinha
    Nov 20, 2006
  2. Chris Newby
    Replies:
    0
    Views:
    446
    Chris Newby
    May 16, 2005
  3. mreister
    Replies:
    1
    Views:
    3,386
    mreister
    May 25, 2010
  4. pantagruel
    Replies:
    0
    Views:
    271
    pantagruel
    Feb 17, 2006
  5. Shao Miller

    Function Called with Compatible Type?

    Shao Miller, Dec 18, 2012, in forum: C Programming
    Replies:
    0
    Views:
    200
    Shao Miller
    Dec 18, 2012
Loading...

Share This Page