Home > Back-end >  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?

Time:02-08

Microsoft extensions to C and C :

To perform the same cast and also maintain ANSI compatibility, you can cast the function pointer to a uintptr_t before you cast it to a data pointer:

int ( * pfunc ) ();
int *pdata;
pdata = ( int * ) (uintptr_t) pfunc;

Rationale for C, Revision 5.10, April-2003:

Even with an explicit cast, it is invalid to convert a function pointer to an object pointer or a pointer to void, or vice versa.

C11:

7.20.1.4 Integer types capable of holding object pointers

Does it mean that pdata = ( int * ) (uintptr_t) pfunc; in invalid?

UPD: As Steve Summit says:

The C standard is written to assume that pointers to different object types, and especially pointers to function as opposed to object types, might have different representations.

CodePudding user response:

Given the definitions

int (*pfunc)();
int *pdata;

, the assignments

pdata = (int *)pfunc;
pdata = (int *)(uintptr_t)pfunc;

are, IMO, equivalent. On a platform where data pointers are of the same size as, or larger than, function pointers, both assignments will work as desired. But on a platform where data pointers are smaller than function pointers, both assignments will inevitably scrape off some of the bits of the function pointer, resulting in a data pointer which can not be converted back to the original function pointer later.

In particular, I believe that both assignments are equivalent despite the presence of the (uintptr_t) cast in the second one. I believe that cast accomplishes precisely nothing.

On a platform where data pointers are smaller than function pointers, and where type uintptr_t is of the same size as data pointers, in the assignment

pdata = (int *)(uintptr_t)pfunc;

, the cast to (uintptr_t) will scrape off some of the bits of pfunc's value.

On a platform where data pointers are smaller than function pointers, and where type uintptr_t is of the same size as function pointers, in the assignment

pdata = (int *)(uintptr_t)pfunc;

, the cast to (int *) will scrape off some of the bits of pfunc's value.

In both cases pdata will end up with only some fraction of pfunc's original value.

(Here I disregard the possibility of architectures with padding bits or the like. On some bizarre, hypothetical platform where function pointers are larger than data pointers, but the extra bits are always 0, both assignments would again work.)

(I've also disregarded the possibility that int * is a different size than void *. I'm not sure whether that would affect the answer, whether a "detour" via void * is more or less un- or necessary when attempting a conversion from int (*)() to int *.)

CodePudding user response:

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. There, the optional (u)intptr_t types are sufficient for round-trip success. Yet we are concerned about a _function pointer, often enough 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 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

CodePudding user response:

Casting to unsigned long long seems a safer choice to silence the warning than using uintptr_t, an optional type that may be smaller than the function pointer.

Note also that the C Standard allows for int * and void * to have a different size and representation albeit no Microsoft compiler supports such exotic targets.

If the goal is to pass a function pointer via an opaque void *, you can always allocate an object of the proper function pointer type, initialize it with pfunc and pass its address:

// setting up the void *
int (*pfunc)();
void *pdata = malloc(sizeof pfunc);
memcpy(pdata, pfunc, sizeof pfunc);

// using the void *
int (**ppfunc)() = pdata;
(*ppfunc)();     // equivalent to (**ppfunc)();
  •  Tags:  
  • Related