0

The function printHello bellow receives a void pointer as argument. But this pointer gets casted to a long and the code works. I don't think I understand how this conversion works. Aren't pointer type supposed to hold addresses? How is a long type suddenly compatible for conversion into a pointer type and vice-versa?

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define NUM_OF_THREADS 5

void *printHello (void *thread_id)
{
    long tid;
    tid = (long) thread_id; // Why is this possible?
    printf("hello from thread #%ld!", tid);
    pthread_exit(NULL);
}

int main()
{
    pthread_t threads[NUM_OF_THREADS]; 
    int return_code;
    long i;

    for(i=0; i<NUM_OF_THREADS; i++)
    {
        printf("In main: creating thread %ld\n", i);

        return_code = pthread_create(&threads[i],NULL,printHello,(void*) i); 
        // Why does it allow to cast 'long i' into '(void*) i'?

        if(return_code) 
        {
            printf("Error: return code from pthread_create is %d\n", return_code);
            exit(-1);
        }
    }
    pthread_exit(NULL);
}

Sample output:

In main: creating thread 0
In main: creating thread 1
hello from thread #0!
hello from thread #1!
In main: creating thread 2
In main: creating thread 3
hello from thread #2!
In main: creating thread 4
hello from thread #3!
hello from thread #4!

2 Answers2

3

An integer may be converted to any pointer type and any pointer type may be converted to an integer type, because the language standard says so. Both addresses and integers are ultimately fixed-length collections of bits, so there's nothing impossible about it.

The result of such conversion is implementation-defined, but it tends to work well in practice. The standard remarks that "mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment".

Barring some unusual representations, if both types have the same number of data bits, the conversion should work both ways. The standard specifies that a pointer can be converted to intptr_t or uintptr_t and back again, which in practice usually means that these types are at least as large as pointers. Lots of older code uses long for the same purpose. There appears to be no guarantee of round trip safety in the other direction, which is needed by this program. It usually works though.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • The standard only says that a pointer to void can be converted to an `intptr_t`, `uintptr_t`, and **then** back again, not that any integer value can be converted to `void *`. – Antti Haapala -- Слава Україні Oct 26 '17 at 09:59
  • @AnttiHaapala you are right, fixed – n. m. could be an AI Oct 26 '17 at 10:34
  • Does this go with the idea that if a pointer variable generally holds the value of an address, where this address is always a number representing a memory location, it implies that if you have any value that derives from an integer type(int, char, long..), you could cast it to a void pointer and this pointer will hold the integer value thinking it is an address but you the programmer know it is not, and therefore use it as you intend? –  Oct 26 '17 at 11:10
  • @TatiZ. Yes this is exactly what's going on here. – n. m. could be an AI Oct 26 '17 at 11:26
  • Thanks a lot guys, I understand now! –  Oct 26 '17 at 11:50
  • Just to point out, what @JeanFrancois mentioned about the size is very important. When I change the `long i ;` to `int i ;` in main(), the code throws a cast error stating: ** cast to pointer from integer of different size [-Wint-to-pointer-cast]** This is because sizeof(void*)=sizeof(long)=8, (at least according to the values being given by my architecture). Therefore, variable types below 8 bytes e.g. int, char would not be eligible for conversion. –  Oct 26 '17 at 12:30
  • @TatiZ. It is a warning. While one should generally strive for warning-free code, this one is benign in these specific circumstances (doing int to pointer to int round trip, that is, from smaller type to larger and back to smaller). It will not be safe to go in the other direction. – n. m. could be an AI Oct 26 '17 at 12:38
1

with (void*) i you're lying to the compiler making it believe that i is an address. Forcing the cast always does something. Doesn't mean it's recommended.

It kind of works (as long as you're not trying to dereference the value) because void * has the same size or bigger than long, but it's definitely implementation defined and should be avoided.

You should pass the pointer on your data instead (it's OK since your variable is declared in the main and has a compatible scope)

 return_code = pthread_create(&threads[i],NULL,printHello,&i); 

and dereference it in your thread code:

void *printHello (void *thread_id)
{
    long tid = *((long *)thread_id);

}
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • if i use `return_code = pthread_create(&threads[i],NULL,printHello,&i); `, I will be **passing the address of i**, which will be a _pass by reference_. If more threads get created, they will be seeing an updated value of **i** rather than their own copy of the variable. Which is not what I intend to do. I do understand the lie that this is telling to the compiler though :) –  Oct 26 '17 at 10:55
  • good point. Either create an array to handle variables separately or pass the value hidden in the pointer. Either way it's not satisfactory; – Jean-François Fabre Oct 26 '17 at 11:11