Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   C Programming (http://www.velocityreviews.com/forums/f42-c-programming.html)
-   -   function called through a non-compatible type (http://www.velocityreviews.com/forums/t438016-function-called-through-a-non-compatible-type.html)

christer-sandberg 05-13-2005 06:27 PM

function called through a non-compatible type
 
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


Eric Sosman 05-13-2005 07:15 PM

Re: function called through a non-compatible type
 


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@sun.com


Keith Thompson 05-13-2005 07:54 PM

Re: function called through a non-compatible type
 
"christer-sandberg" <christer.sandberg@mdh.se> 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) 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.

christer-sandberg 05-13-2005 09:08 PM

Re: function called through a non-compatible type
 

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


Keith Thompson 05-13-2005 09:24 PM

Re: function called through a non-compatible type
 
"christer-sandberg" <christer.sandberg@mdh.se> 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) 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.

christer-sandberg 05-13-2005 09:45 PM

Re: function called through a non-compatible type
 
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


Eric Sosman 05-13-2005 09:56 PM

Re: function called through a non-compatible type
 


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@sun.com


Keith Thompson 05-13-2005 10:36 PM

Re: function called through a non-compatible type
 
"christer-sandberg" <christer.sandberg@mdh.se> 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) 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.

Bilgehan.Balban@gmail.com 05-14-2005 12:55 AM

Re: function called through a non-compatible type
 
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


Keith Thompson 05-14-2005 03:20 AM

Re: function called through a non-compatible type
 
Bilgehan.Balban@gmail.com 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) 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.


All times are GMT. The time now is 07:47 AM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.