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 thanint
, plusfloat
.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
How to Get Screenshot of a Window as Bitmap Object in C++
C++, How to Determine If a Windows Process Is Running
Const Correctness for Value Parameters
C++: Rationale Behind Hiding Rule
Is the Use of Std::Vector<Bool> Objects in C++ Acceptable, or Should I Use an Alternative
Stl Remove Doesn't Work as Expected
More Elegant Way to Check for Duplicates in C++ Array
Order of Execution in Constructor Initialization List
Do I Really Need to Implement User-Provided Constructor for Const Objects
Does a C++ Struct Have a Default Constructor
What Is Copy Elision and How Does It Optimize the Copy-And-Swap Idiom
Pseudo-Destructor Call Does Not Destroy an Object
Uses of a C++ Arithmetic Promotion Header
What Is the Value Category of the Operands of C++ Operators When Unspecified