1

i try to test multithreading in c language and on Manjaro OS. i write a small code but i faced a strange problem

i execute below simple code but i didn't get expected result:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#define THREADS 4

void *routine1(void * x)
{
    int result =2;
    pthread_exit((void *)result);
}

int main ()
{
    int sum=0;
    int retval=0;
   pthread_t threads[THREADS];
    for ( int i=0;i<THREADS;i++)
        pthread_create(&threads[i], NULL, routine1, (void *)i );
   for (int i=0; i<THREADS; i++)
   {
     pthread_join(threads[i],&retval);
     sum+=retval;
   }
   printf("%d\n",sum);
   return 0;

}

the result of above code is:

2

but i expected to see 8 value on output. after debugging for some hours i found that if i declare the sum variable after pthread_create() function, the code will run normally:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#define THREADS 4

void *routine1(void * x)
{
    int result =2;
    pthread_exit((void *)result);
}

int main ()
{
    int retval=0;
   pthread_t threads[THREADS];
   for ( int i=0;i<THREADS;i++)
   pthread_create(&threads[i], NULL, routine1, (void *)i );
   int sum=0;
   for (int i=0; i<THREADS; i++)
   {
        pthread_join(threads[i],&retval);
        sum+=retval;
   }
   printf("%d\n",sum);
   return 0;
}

the output of code is:

 8

which is right answer.

i want to know why the first code is wrong? it is because of pthread_create() function?

NOTE: if you run this code don't care about warnings, they are all about casting

milad
  • 1,854
  • 2
  • 11
  • 27

1 Answers1

4

No, it's not the order. It's the type of retval. When, passing &retval to pthread_join, it's the wrong type/sizeof.

When retval is an int, it's only 4 bytes, but [assuming 64 bit compile], pthread_join expects a pointer to void *, which is 8 bytes.

This causes undefined behavior because the call will write 8 bytes into a variable that is only 4 bytes. The remaining 4 bytes go beyond the size of retval and may overwrite anything on the stack.

There was the same undefined behavior in your second example, but we just "got lucky" and got the expected result [probably because the order of variables in the stack frame changed to avoid the UB from producing an unexpected result]. But, both still had UB.

Also, when casting to/from a pointer, we should use long instead of int [to prevent compiler warnings].

Here's the corrected code:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#define THREADS 4

void *
routine1(void *x)
{
#if 0
    int result = 2;
#else
    long result = 2;
#endif

    pthread_exit((void *) result);
}

int
main()
{
    int sum = 0;
#if 0
    int retval = 0;
#else
    void *retval;
#endif
    pthread_t threads[THREADS];

#if 0
    for (int i = 0; i < THREADS; i++)
        pthread_create(&threads[i], NULL, routine1, (void *) i);
#else
    for (long i = 0; i < THREADS; i++)
        pthread_create(&threads[i], NULL, routine1, (void *) i);
#endif

    for (int i = 0; i < THREADS; i++) {
        pthread_join(threads[i], &retval);
#if 0
        sum += retval;
#else
        sum += (long) retval;
#endif
    }

    printf("%d\n", sum);

    return 0;

}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • sir, even usage of long doesn't cause ub? – Soner from The Ottoman Empire May 12 '19 at 19:29
  • @snr No, using `long` prevents the warning _cast to pointer from integer of different size [-Wint-to-pointer-cast]_. Here, it doesn't matter much, but in the general case, casting a pointer to an int can/will chop off the high 32 bits, so if they're non-zero, the resulting value is truncated. Not UB, just a wrong value. – Craig Estey May 12 '19 at 19:42
  • No, we should use `uintptr_t`. `long` isn't guaranteed to be the size of a pointe. – S.S. Anne Oct 01 '19 at 20:08
  • @JL2210 This is actually dictated by the architecture. See: http://www.unix.org/version2/whatsnew/lp64_wp.html and look at the table. That's the spec that all modern arches adhere to and were designed around. `long` is fine for all models except `LLP64` and _only_ MS/Win* uses that one [And, I don't do windows ;-)]. Virtually all others use `LP64` [with `long long` being 64]. – Craig Estey Oct 02 '19 at 01:16