12

I'm trying to pick my C skills again. I want to sum a sequence in different threads, each thread would return a pointer of the sum of a part of the sequence. However, when I tried to convert the void* type value local_sum to int, problem occurred.

I tried to convert with sum += *(int*)local_sum;, a segment error occurred and I got Process finished with exit code 11.

I found that if I use sum += (int)local_sum;, it would be okay. But I couldn't convince myself: shouldn't local_sum be a void *? Why it can be converted to int with (int)local_sum?

I'm so grateful it you could answer the problem.

The part that sum each process's return value is here:

int sum = 0;
for (int i = 0; i < NUM_THREADS; i ++) {
    void * local_sum;
    pthread_join(count_threads[i], (&local_sum));
    sum += (int)local_sum;
}

The function of a thread is here:

void * count_thr(void *arg) {
    int terminal = ARRAY_SIZE / NUM_THREADS;
    int sum = 0;
    for (int i = 0; i < terminal; i ++) {
        sum += *((int*)arg + i);
    }
    return (void*)sum;
}
Frost-Lee
  • 420
  • 1
  • 5
  • 20
  • 12
    Passing an `int` inside the the `void*` of pthreads, as opposed to pointing at one, has always been a dirty hack. And as all dirty hacks, they eventually break. Integer-to-pointer conversions have implementation-defined behavior (at the very least use uintptr_t). The solution is not to use dirty hacks, but to pass a pointer to malloc:ed data. The overhead execution time of dynamic allocation is nothing compared to the cost of creating and closing a thread. – Lundin Nov 16 '18 at 15:41
  • refer to [c89: Convert an int to void* and back](https://stackoverflow.com/questions/7042368/c89-convert-an-int-to-void-and-back) and the linked question [Is it safe to cast an int to void pointer and back to int again?](https://stackoverflow.com/questions/3568069/is-it-safe-to-cast-an-int-to-void-pointer-and-back-to-int-again) – Sander De Dycker Nov 16 '18 at 15:46

3 Answers3

10

You're returning the value of int sum by setting a void * address to it. In this case, the address is not valid. But, if you keep that in mind and get the value of sum by casting a void * to int it will work.

void * is used this way sometimes to return either a value (e.g. int) or an address to something (e.g. struct).

To illustrate this:

int a = 5;
void *p = (void *)a;
int b = (int)p;

a, p, and b all have a value of 5. p does not point to a valid address. Trying to dereference p would result in undefined behavior:

b = *(int *)p; // Undefined Behavior!

Consider the following program:

#include <limits.h>
#include <stdio.h>

int main(void)
{
    int a, b;
    void *p;

    a = 5;
    p = (void *)a;
    b = (int)p;

    printf("%d %p %d\n", a, p, b);

    a = INT_MAX;
    p = (void *)a + 1;
    b = (int)p;

    printf("%d %p %d\n", a, p, b);

    return 0;
}

When compiled, I get the following warnings:

$ gcc main.c -o main.exe
main.c: In function ‘main’:
main.c:9:9: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
     p = (void *)a;
         ^
main.c:10:9: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     b = (int)p;

...

A warning is issued because, as pointed out by @Gerhardh, the sizeof(int) and the sizeof(void *) may be different. You may suffer data loss if the value of the void * exceeds the maximum value a int can hold.

Output

$ ./main.exe
5 0x5 5
2147483647 0x80000000 -2147483648
Fiddling Bits
  • 8,712
  • 3
  • 28
  • 46
  • 1
    " in our case, this behavior is desired." Really? It's not only a warning about conversion, it is about different size. That is not the part that is desired. You probably have 4 byte integers and 8 byte pointers. – Gerhardh Nov 16 '18 at 15:47
  • 2
    note that the behavior of this is implementation defined – Sander De Dycker Nov 16 '18 at 15:47
  • *a, p, and b all have a value of 5.* `a` and `b` are not guaranteed to have the same value. – David Brown Nov 16 '18 at 20:36
  • @DavidBrown Of course, this is undefined behavior. On my machine, this behavior is so. – Fiddling Bits Nov 16 '18 at 20:38
  • @FiddlingBits : correction : it's [implementation defined](https://stackoverflow.com/questions/7042368/c89-convert-an-int-to-void-and-back) as I stated earlier. It will work on many (if not most) platforms as you describe, but is not guaranteed to. – Sander De Dycker Nov 19 '18 at 07:36
8

You can't do *(int*)local_sum because local_sum is not an int* cast to void*. local_sum is an int cast to void*. It is a number reinterpreted as an address, but only for transfer purposes, because pthread_exit only allows you to return a void*, not an int and because the standard explicitly allows implementation-defined conversion (6.3.2.3p5, 6.3.2.3p6) between integers and numbers as long as the values fit (if they don't then, UB). If you return, e.g., 0x42, it is highly unlikely there's anything at address 0x42, so you should forget about dereferencing it and instead you should convert it back to an integer ASAP, either with (int)local_sum; or perhaps better with (int)(intptr_t)local_sum; (though intptr_t isn't guaranteed to exist) or (perhaps best) with (int)(intmax_t)local_sum; so as to avoid possible compiler warnings about converting to an integer of a different size on LP64 platforms.

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
1

A secure and portable solution could be the use of an union:

union void_cast {
    void* ptr;
    int value;
};

Then for example you can safely reinterpret a void* pointer with:

int VOID_TO_INT(void* ptr) {
    union void_cast u;
    u.ptr = ptr;
    return u.value;
}

void* INT_TO_VOID(int value) {
    union void_cast u;
    u.value = value;
    return u.ptr;
}

So your code can be changed to:

sum += VOID_TO_INT(local_sum);
Morpheus
  • 377
  • 2
  • 9
  • 2
    Casting should work regardless of endianness. On a (very theoretical) big-endian machine where ints are half the width of pointers, the union approach would fail to extract the value while a cast shouldn't. – Petr Skocik Nov 16 '18 at 16:21