C++: Is It Safe to Cast Pointer to Int and Later Back to Pointer Again

Is it safe to cast an int to void pointer and back to int again?

In most modern-day commonplace machines, probably.

However, I'd bet that there is some obscure compiler or configuration (say, a 16-bit addressed machine that uses 32-bit integer arithmetic) where that is not the case.

A uintptr_t is guaranteed to hold both, though, so use that type if you want to.

Is it safe to cast a function pointer to another function pointer in C?

Is it safe to cast these 2 functions to callback pointer type then call them without casting back?

No. The types of the two functions are not compatible with type callback, in the language specification's sense of "compatible", therefore calling either of those functions via a pointer of type callback invokes undefined behavior. Overall, non-variadic function types are never compatible with variadic ones, and in practice, many implementations use different calling conventions for one type than for the other, such that there is no plausible reason even to hope that calling a function of one variety as if it were of the other variety would have the desired effect in any consistent way.

You have several alternatives, among them:

  • Use different callback types for different purposes, each appropriate to its intended callback interface. This way you can avoid casting the callback functions at all. This would be my recommendation. It achieves the best type safety, and you need somehow to keep track of what the actual callback type is anyway, so that you can call it correctly.

  • Use a union of function pointer types. Callback specifiers assign to the appropriate member of the union, and callback callers select the appropriate member.

    typedef union {
    int (*unary)(int i);
    int (*binary)(int i, int j);
    } callback;

    // ...

    callback cb1 = { .unary = foo };
    callback cb2 = { .binary = foo2 };
    cb1.unary(1);
    cb2.binary(1, 2);

    You might even use a tagged union -- one that additionally carries information about which member is used. That would be a bit more complicated to use, but it would give you a means to achieve additional type safety. One of the variations on this approach would be my fallback recommendation if you need a single data type with which multiple callback types can be conveyed.

  • Choose a single callback type that meets all your needs. One way to do that would be to give it a parameter of type void *, by which callback functions can accept any number and type of inputs by, for example, a pointer to a suitable structure type.

    typedef int (*callback)(void *);
    struct one_int { int i1; };
    struct two_int { int i1, i2; };
    int foo(void *args) {
    struct one_int *one_int = args; // ...
    }
    int foo2(void *args) {
    struct two_int *two_int = args; // ...
    }
  • Choose any function type as callback. Cast to that type going in, and back to the original type for calls.

  • Specify the callback type without a prototype. In C, if a function declaration that is not part of a definition of that function does not specify a parameter type list then that means that no information is provided about the parameters (unlike in C++, where that means that the function has no parameters). That is compatible with functions requiring any specific number of arguments -- but not variadic ones -- provided that applying the default argument promotions to the parameter types yields compatible types. Type int is a fine parameter type in that regard. The main ones that would be a problem are integer types narrower than int, plus float.

    typedef int (*callback)();

    This would allow exactly the usage you describe for the particular function types in your example.

    callback cb1 = foo;
    callback cb2 = foo2;

    (*cb1)(1); // or just cb1(1)
    (*cb2)(1, 2); // or just cb2(1, 2)

    Contrary to another answer's claim, support for this approach does not constitute an extension to any version of the C language specification published to date. Supporting it is a requirement for conformance with any of C89, C99, C11, and C17. However, it has been declared "obsolescent" in C17, which constitutes a warning that it may be removed from some future version of the language specification. I expect that it indeed will be removed, possibly as soon as the next version of the specification, though obsolescence does not guarantee that.

Cast int to pointer - why cast to long first? (as in p = (void*) 42; )

The glib documentation is wrong, both for their (freely chosen) example, and in general.

gpointer p;
int i;
p = (void*) 42;
i = (int) p;

and

gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;

will both lead to identical values of i and p on all conforming c implementations.

The example is poorly chosen, because 42 is guaranteed to be representable by int and long (C11 draft standard n157: 5.2.4.2.1 Sizes of integer types ).

A more illustrative (and testable) example would be

int f(int x)
{
void *p = (void*) x;
int r = (int)p;
return r;
}

This will round-trip the int-value iff void* can represent every value that int can, which practically means sizeof(int) <= sizeof(void*) (theoretically: padding bits, yadda, yadda, doesn't actually matter). For other integer types, same problem, same actual rule (sizeof(integer_type) <= sizeof(void*)).

Conversely, the real problem, properly illustrated:

void *p(void *x)
{
char c = (char)x;
void *r = (void*)c;
return r;
}

Wow, that can't possibly work, right? (actually, it might).
In order to round-trip a pointer (which software has done unnecessarily for a long time), you also have to ensure that the integer type you round-trip through can unambiguously represent every possible value of the pointer type.

Historically, much software was written by monkeys that assumed that pointers could round-trip through int, possibly because of K&R c's implicit int-"feature" and lots of people forgetting to #include <stdlib.h> and then casting the result of malloc() to a pointer type, thus accidentally roundtripping through int. On the machines the code was developed for sizeof(int) == sizeof(void*), so this worked. When the switch to 64-bit machines, with 64-bit addresses (pointers) happened, a lot of software expected two mutually exclusive things:

1) int is a 32-bit 2's complement integer (typically also expecting signed overflow to wrap around)

2) sizeof(int) == sizeof(void*)

Some systems (cough Windows cough) also assumed sizeof(long) == sizeof(int), most others had 64-bit long.

Consequently, on most systems, changing the round-tripping intermediate integer type to long fixed the (unnecessarily broken) code:

void *p(void *x)
{
long l = (long)x;
void *r = (void*)l;
return r;
}

except of course, on Windows. On the plus side, for most non-Windows (and non 16-bit) systems sizeof(long) == sizeof(void*) is true, so the round-trip works both ways.

So:

  • the example is wrong
  • the type chosen to guarantee round-trip doesn't guarantee round-trip

Of course, the c standard has a (naturally standard-conforming) solution in intptr_t/uintptr_t (C11 draft standard n1570: 7.20.1.4 Integer types capable of holding object pointers), which are specified to guarantee the

pointer -> integer type -> pointer

round-trip (though not the reverse).

Is conversion of a function pointer to a uintptr_t / intptr_t invalid?

Is conversion of a function pointer to a uintptr_t / intptr_t invalid?

No. It may be valid. It may be undefined behavior.


Conversion of a function pointer to ìnt* is not defined. Nor to any object pointer. Nor to void *.

pdata = ( int * ) pfunc; is undefined behavior.

Conversion of a function pointer to an integer type is allowed, with restrictions:

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. C17dr 6.3.2.3 6

Also integer to a pointer type is allowed.

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. C17dr 6.3.2.3 6

void * to integer to void * is defined. Object pointer to/from void* is defined. Then the optional (u)intptr_t types are sufficient for round-trip success. Yet we are concerned about a function pointer. Often enough function pointers are wider than an int *.

Thus converting a function pointer to int * only makes sense through an integer type, wider the better.

VS may recommend through the optional type uintptr_t and is likely sufficient if information is lossless on other platforms. Yet uintmax_t may afford less loss of information, especially in the function pointer to integer step, so I pedantically suggest:

pdata = ( int * ) (uintmax_t) pfunc;

Regardless of the steps taken, code is likely to become implementation specific and deserves guards.

#ifdef this && that
pdata = ( int * ) (uintmax_t) pfunc;
#else
#error TBD code
#endif

Is it always safe to convert an integer value to void* and back again in POSIX?

As you say, C99 doesn't guarantee that any integer type may be converted to void* and back again without loss of information. It does make a similar guarantee for intptr_t and uintptr_t defined in <stdint.h>, but those types are optional. (The guarantee is that a void* may be converted to {u,}intptr_t and back without loss of information; there's no such guarantee for arbitrary integer values.)

POSIX doesn't appear to make any such guarantee either.

The POSIX description of <limits.h> requires int and unsigned int to be at least 32 bits. This exceeds the C99 requirement that they be at least 16 bits. (Actually, the requirements are in terms of ranges, not sizes, but the effect is that int and unsigned int must be at least 32 (under POSIX) or 16 (under C99) bits, since C99 requires a binary representation.)

The POSIX description of <stdint.h> says that intptr_t and uintptr_t must be at least 16 bits, the same requirement imposed by the C standard. Since void* can be converted to intptr_t and back again without loss of information, this implies that void* may be as small as 16 bits. Combine that with the POSIX requirement that int is at least 32 bits (and the POSIX and C requirement that long is at least 32 bits), and it's possible that a void* just isn't big enough to hold an int or long value without loss of information.

The POSIX description of pthread_create() doesn't contradict this. It merely says that arg (the void* 4th argument to pthread_create()) is passed to start_routine(). Presumably the intent is that arg points to some data that start_routine() can use. POSIX has no examples showing the usage of arg.

You can see the POSIX standard here; you have to create a free account to access it.



Related Topics



Leave a reply



Submit