Is storing a byte in a void pointer cross-platform safe?

198 views Asked by At
void *vp = (void *)5;
uint8_t i = (uint8_t)vp;

Will i == 5 on all 8-bit and higher cpus? What are the risks doing this? Is there a better way to have a variable store either an 8-bit integer literal or a pointer in C99?

I have an array of function pointers to functions that take a void *. Some functions need to interpret the void * as a uint8_t.

2

There are 2 answers

2
Eric Postpischil On BEST ANSWER

void *vp = 5; should not compile; the C standard at least requires the compiler to issue a diagnostic message. You can request the conversion with void *vp = (void *) 5;, and you can request the reverse conversion with (uint8_t) vp. The C standard does not guarantee this will reproduce the original value. (Conversions involving pointers are specified in C 2018 6.3.2.3.) It is likely to work in most C implementations.

An alternative that would be defined by the C standard would be to use offsets into some sufficiently large object you already have. For example, if you have some array A, and you want to store some small number n in a void *, then you can do:

void *vp = (char *) A + n; // Point n bytes into the object A.

and you can recover the number with:

(char *) vp - (char *) A // Subtract base address to recover offset.
2
dbush On

The C standard does allow for conversions between an integer and a pointer, however it doesn't explain exactly how it should happen. That is left up to each specific implementation.

Section 6.3.2.3 p5-6 of the C standard describes these conversions:

5 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.

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

Under gcc in particular what you're doing will work, however it's not guaranteed to work on all compilers or architectures.

What is guaranteed to work however is to take the address of a compound literal:

void *vp = &(uint8_t){5};
uint8_t i = *(uint8_t *)vp;

This creates a temporary object of type uint8_t and takes its address. This address can then be converted to a void * and back which is fully standard compliant, as per paragraph 1 of 6.3.2.3:

A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

The lifetime of the compound literal is that of the block in which it is defined. So as long the pointer isn't used after that block ends it will work.

If however you intend to pass it to a function that starts a thread, you're better off dynamically allocating memory for the value and passing that. Otherwise, you run the risk of the function where the compound literal is defined returning while the thread function is running which could attempt to use that pointer.