Quantumboredom Quantumboredom - 1 year ago 63
C Question

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

This question is almost a duplicate of some others I've found, but this specifically concerns POSIX, and a very common example in pthreads that I've encountered several times. I'm mostly concerned with the current state of affairs (i.e., C99 and POSIX.1-2008 or later), but any interesting historical information is of course interesting as well.

The question basically boils down to whether b will always take the same value as a in the following code:

long int a = /* some valid value */
void *ptr = (void *)a;
long int b = (long int)ptr;

I am aware that this usually works, but the question is whether it is a proper thing to do (i.e., does the C99 and/or POSIX standards guarantee that it will work).

When it comes to C99 it seems it does not, we have

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.56)

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.

Even using intptr_t the standard seems to only guarantee that any valid void* can be converted to intptr_t and back again, but it does not guarantee that any intptr_t can be converted to void* and back again.

However it is still possible that the POSIX standard allows this.

I have no great desire to use a void* as a storage space for any variable (I find it pretty ugly even if POSIX should allow it), but I feel I have to ask because of the common example use of the pthreads_create function where the argument to start_routine is an integer, and it is passed in as void* and converted to int or long int in the start_routine function. For example this manpage has such an example (see link for full code):

//Last argument casts int to void *
pthread_create(&tid[i], NULL, sleeping, (void *)SLEEP_TIME);
/* ... */
void * sleeping(void *arg){
//Casting void * back to int
int sleep_time = (int)arg;
/* ... */

I've also seen a similar example in a textbook (An Introduction to Parallel Programming by Peter S. Pacheco). Considering that it seems to be a common example used by people who should know this stuff much better than me, I'm wondering if I'm wrong and this is actually a safe and portable thing to be doing.

Answer Source

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download