tags:

views:

222

answers:

4

The whole code is written in ANSI C, and it should remain so. I have a callback defined like so:

typedef enum {
    Event_One,
    Event_Two,
    Event_State
} EventEnum;

typedef void (*callback)(EventEnum event, void* data);

The callback recipient interprets data depending on the event value. This is the contract between the components. Sometimes it is a pointer to the structure, sometimes it might be a string, other cases might be other data. I am defining an additional event and setting up a new "contract" that data is an enumeration. Like so:

typedef enum {
    State_Initial = 0,
    State_Running,
    State_Final
} StateEnum;

Then somewhere in the code I have a callback function, which is doing this

void ProcessEvent (EventEnum event, void* data)
{
    if (event == Event_State)
    {
         StateEnum state = (StateEnum)data; /* <<<<<<<<<<< */
         switch (state) {
         case State_Initial:
             <...>
             break;
         case State_Running:
             <...>
             break;
         case State_Final:
             <...>
             break;
         }
    }
}

The callback above is called like so:

{
    callback infoCallback = ProcessEvent; /* This is only for example,
                                             done during initialization */
    <...>
    StateEnum someState = State_Running;
    <...>
    infoCallback(Event_State, (void*)someState); /* <<<<<<<<<<<<<<<<<<< */
}

Is there anything fundamentally wrong with typecasting void* to StateEnum and vice versa? What are the possible gotchas in this way? Any thoughts on testability and maintainability?

EDIT: The code compiles, links and runs OK right now. I want to know why this should not be done and if there are any real reasons why the code must be changed.

+3  A: 

Only pointers to objects (i.e., not functions) can be converted to void * and back. You can't convert a non-pointer to void * and back. So, change your call to:

infoCallback(Event_State, &someState);

And your function to:

StateEnum *state = data;
switch (*state)
...

From the standard (6.3.2.3):

An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.

Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.

So, what you are doing is implementation-defined. If your implementation defines it to be OK to convert int to a pointer and back, then the code will work. In general, it is not portable. For more details, see this thread on comp.lang.c.

C99 additionally defines types intptr_t and uintptr_t, which are integral types, and it is guaranteed to convert a void * to them and back.

Alok
You can assign anything to a void*
yodaj007
@yodaj007: Chapter and verse from the standard please. I can see this in n1256 6.3.2.2p1: "A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer." Nothing about "anything", just "pointer to any incomplete or object type".
Alok
@Alok: "any incomplete or object type" is essentially the standard's way of saying any type. You can cast pointer types to any other kind of pointer type all day long.
Billy ONeal
@BillyONeal: But "any incomplete or object type" is prefixed by *A pointer to*. He is not casting a pointer to another pointer.
Alok
Wasn't this the exact reason that there was a technical corrigendum to POSIX 2003 for `dlsym`? `dlsym` can be used to dynamically find function pointers at runtime, but the C99 standard says converting `void *` (the return value of `dlsym`) to a function pointer is undefined, so a technical corrigendum had to be released to explain a valid workaround. This can be found in many manpages for `dlsym`.
dreamlax
@dreamlax: you are right. I just found the RATIONALE on http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html. Thanks for the information!
Alok
@Alok: FWIW I upvoted this answer.
dreamlax
BillyONeal: You can cast any *non-function-pointer* pointer back and forth to qualified `void *` or `char *`, it is true. But that says nothing about casting *non-pointer* types to `void *`, which is what the question is about.
caf
@Cal: I believe Alok said that a few comments ago. But you can cast function pointers all day long. A function pointer is an "incomplete or object type" -- you declare it with a typedef no?
Billy ONeal
@BillyONeal: A function pointer is not "a pointer to an incomplete of object" type. You seem to keep ignoring "a pointer to".
Alok
A function pointer is a pointer to a function. How am I forgetting "A pointer to"?
Billy ONeal
`typedef int (*callback_t)(); callback_t callbackPtr = mycallback; void * evil = (void *)callbackPtr; callback_t callback2 = (callback_t) evil; callback2();` runs just fine.
Billy ONeal
@BillyONeal: It may run fine on your computer, but it's not guaranteed to according to the standard. Try omitting the casts `(void *)` and `(callback_t)` and your compiler will warn you. Now try `int i; void *p = int *pi = p;`, and it won't warn you. Not no casts.
Alok
@BillyONeal: In fact, gcc's warning makes it very clear: "ISO C forbids conversion of function pointer to object pointer type".
Alok
`int i; void *p = int *pi = p;` does not compile. Conversion between incompatible pointer types requires a C-Style cast or `reinterpret_cast`.
Billy ONeal
@BillyONeal: the question is tagged C, not C++.
Alok
@BillyONeal, you are compiling that with a C++ compiler. When I compile it with my C compiler it compiles without warning.
dreamlax
@dreamlax: Gee, what gave it away. Was it `reinterpret_cast` ? :P
Billy ONeal
@BillyONeal: Woah! I completely overlooked that. I read the first sentence and then jumped straight to writing a comment . . . but, we have been talking about C99 this whole time :)
dreamlax
@dreamlax: I was assuming it was C89, which would be roughly the same as C++ with regard to this pointer casting stuff -- with the one exception that C allows an implicit cast to void *.
Billy ONeal
@BillyONeal, there is no difference between C89 and C99 as far as conversion to/from `void *` is involved.
Alok
@Alok: Thanks for the tip :)
Billy ONeal
A: 

What you are doing is implementation defined. There is no guarantee that the enum will have an identical value after being converted to a point-to-void and then converted back. If you really need to be sure, you should use intptr_t or uintptr_t instead of an enum. They are guaranteed to have the same value after being cast to a pointer-to-void and back again.

That will work, but note that `intptr_t` and `uintptr_t` are optional - a conforming implementation need not provide them.
caf
A: 

Alok is absolutely correct that conversions between integers and pointers are implementation-defined, and hence entirely non-portable. It would seem to be completely allowable, for example, if all casts of void * values to integral values always produced 0 (on an implementation that does not provide intptr_t or uintptr_t, anyway).

A common idiom is to extend the contract such that the callback will receive a pointer to a dynamically allocated enum, which it must free. In the caller:

StateEnum *someState = malloc(sizeof *someState);
*someState = State_Running;

infoCallback(Event_State, someState);

...and in the callback:

void ProcessEvent (EventEnum event, void* data)
{
    if (event == Event_State)
    {
         StateEnum state = *(StateEnum *)data;
         free(data);
         switch (state) {
caf
The problem here is that malloc would impose a performance overhead and is an additional point for potential failure (out of memory). The callback caller should be doing the freeing.
Ignas Limanauskas
When possible, that's definitely preferable (and in that case, you can often use a `union` type for the `data`). However, it is often the case that the `data` is supplied at callback-registration time, and the callback is actually called much later - long after the function that originally registered the callback has finished. It really depends on exactly what you're doing.
caf
A: 

Although the compiler allows this, and in some cases it may be ok you need to be really careful about the lifetime of the objects.

The main problem I can see is that unless the processEvent fuction occurs in the same call chain as infoCallback, the original someState variable may go out of scope. So later when you refer to is in infoCallback, the void* will be undefined.

Jeremy Simon