2

I'm debugging a program from a book. The program appears to work but I do not understand one line which I comment below.

#include <pthread.h>
#include <stdio.h>
/* Compute successive prime numbers (very inefficiently). Return the
Nth prime number, where N is the value pointed to by *ARG. */
void* compute_prime (void* arg)
{
int candidate = 2;
int n = *((int*) arg);
while (1) {
int factor;
int is_prime = 1;
/* Test primality by successive division. */
for (factor = 2; factor < candidate; ++factor)
if (candidate % factor == 0) {
is_prime = 0;
break;
}
/* Is this the prime number we’re looking for? */
if (is_prime) {
if (--n == 0)
/* Return the desired prime number as the thread return value. */
return (void*) candidate;    // why is this casting valid? (candidate is not even a pointer)
}
++candidate;

}
return NULL;
}
int main ()
{
pthread_t thread;
int which_prime = 5000;
int prime;
/* Start the computing thread, up to the 5,000th prime number. */
pthread_create (&thread, NULL, &compute_prime, &which_prime);
/* Do some other work here... */
/* Wait for the prime number thread to complete, and get the result. */
pthread_join (thread, (void*) &prime);
/* Print the largest prime it computed. */
printf(“The %dth prime number is %d.\n”, which_prime, prime);
return 0;
}

3 Answers3

5

It's not valid. It simply happens to work if sizeof(int) == sizeof(void *), which happens on many systems.

A void * is only guaranteed to be able to hold pointers to data objects.

Here is a C FAQ on the subject.

How are integers converted to and from pointers? Can I temporarily stuff an integer into a pointer, or vice versa?

Pointer-to-integer and integer-to-pointer conversions are implementation-defined (see question 11.33), and there is no longer any guarantee that pointers can be converted to integers and back, without change

Forcing pointers into integers, or integers into pointers, has never been good practice

cnicutar
  • 178,505
  • 25
  • 365
  • 392
  • I'd return the book :) This is indeed a hack, albeit a common one (stashing values in pointers, depending on equal type width). – nielsj Jul 31 '11 at 12:53
  • POSIX does seem to countenance this hack: for example, `SIG_IGN`. – Joseph Quinsey Jul 31 '11 at 14:02
  • @Joseph Quinsey I know; I also read R..'s answer and I agree. Still, it's much too ugly to personally use. – cnicutar Jul 31 '11 at 14:03
  • POSIX does not countenance it. SIG_IGN is defined (primarily in the C spec, rather than POSIX) as having "a type compatible with" the relevant function pointer type. The fact that some implementations achieve this by casting a small integer value is no more than an implementation detail of those implementations. – Richard Kettlewell Aug 01 '11 at 07:50
1

What do you mean by "valid"?

You're explicitly requesting a cast, and the language or the compiler won't stop you. Whether it's useful is an entirely different matter. Indeed, as you say, candidate isn't a pointer and it's not pointing to anything useful. The recipient of the return value will have to know what to do with it (e.g. cast it back to int, though it's not guaranteed that that gives you back the original value).

If you never need the value, you might just return 0. It's simply that pthread_create expects as an argument a function pointer of type void*(*)(void*), so your function has to return a void*. If you don't need it, you can ignore it and just return any old value.

(In other situations, your thread function might have chosen to malloc() some memory, fill it with result data and return a pointer to that address, which is why void* is in some sense the "most general" return type for a C function that needs to be as flexible as possible without knowing how it's going to be used.)

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • If the function result were to be discarded anyway, it could return NULL, which is probably safer than an invalid pointer. I assume it is supposed to return an integer alright, but this is indeed a hack. – Rudy Velthuis Jul 31 '11 at 13:03
  • @Rudy: Yeah, I see, the return value is actually used stored in `pthread_join`. The clean solution would be to store the return value in a preallocated area of memory that's passed to the function as the argument. – Kerrek SB Jul 31 '11 at 13:10
  • That was my thought too. No matter how you do it, it is hard to do this nicely. – Rudy Velthuis Jul 31 '11 at 13:21
1

While others are right that C leaves the results of this cast implementation-defined, your code (using pthreads) depends on POSIX, which requires a memory model where a compiler would have to go out of its way to break what you're doing. Also all real-world POSIX implementations are ILP32 or LP64 meaning that any value of int will fit in a pointer.

While "ugly" from a formal standpoint, using int-to-void * casts with the thread argument or return value to pass small integer data is usually the lesser of two evils, the other choice being malloc(sizeof(int)) in one thread and free in the other, which not only fundamentally adds significant costs, but also can be pathologically bad on some allocators tuned only for the case where the allocating thread and freeing thread are the same.

One way you can avoid both evils: if the creator of the thread will be sticking around for a while and eventually calling pthread_join, you could pass the new thread a pointer to a data object on the creator's stack or in memory otherwise owned by the creator. The new thread can then write its result to this location before it terminates.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711