Разыменование void * просто как (int) - стандартная практика? - PullRequest
2 голосов
/ 26 марта 2020

Я пытался напечатать возвращаемое значение потока и обнаружил, что меня все еще смущает понятие двойных указателей void.

Насколько я понимаю, void * - это указатель на любой тип данных, который может быть разыменовано с помощью соответствующего приведения, но в противном случае «уровни» ссылок сохраняются, как с обычными типизированными указателями (т.е. вы не можете ожидать получить то же значение, которое вы поместили в **(int **)depth2, разыменовав его только один раз, например *depth2.)

В коде (ниже), который я собрал для своей нити-возврата-печати, кажется, что я не разыменую * пустой указатель вообще, когда я просто приведу его к (int) . Это случай использования адреса в качестве значения? Если это так, это нормальный способ возврата из потоков? Иначе чего мне не хватает ??

(Мне известно, что более безопасный способ манипулировать данными внутри потока может быть хранилищем на уровне вызывающего, но мне очень интересен этот случай и то, что я не понимаю в указателе void .)

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *myThread(void *arg)
{
    return (void *)42;
}

int main()
{
    pthread_t tid;
    void *res;                                        // res is itself a void *

    pthread_create(&tid, NULL, myThread, NULL);
    pthread_join(tid, &res);                          // i pass its address, so void** now
    printf(" %d \n", (int)res);                       // how come am i able to just use it as plain int?

    return 0;
}

1 Ответ

3 голосов
/ 27 марта 2020

Прежде всего, цель pthread_join() состоит в том, чтобы обновить void *, заданный через его второй аргумент, чтобы получить результат функции потока (a void *).
Когда вам нужно обновить int как в scanf("%d", &my_var); аргумент - это адрес обновляемого int: int *.
По тем же причинам вы обновляете void *, предоставляя void **.

В конкретной ситуации c вашего примера мы не используем возвращенный void * обычным образом: это хитрость!
Поскольку указатель можно рассматривать как большое целое число Считая байты в очень длинной строке, уловка состоит в том, чтобы предположить, что этот указатель может просто хранить целочисленное значение, которое не относится ни к какой ячейке памяти.

В вашем примере возвращение (void *)42 эквивалентно говоря «вы найдете что-то интересное по адресу 42».
Но по этому адресу ничего не было размещено!
Это проблема? Нет, пока никто не пытается разыменовать этот указатель для извлечения чего-либо по адресу 42.

После выполнения pthread_join() переменная res обновлена ​​и содержит возвращенные void *: В данном случае 42.
Мы выполняем обратный трюк, предполагая, что информация, запомненная в этом указателе, не относится к ячейке памяти, а является простым целым числом.

Это работает, но это очень уродливо !
Основным преимуществом является то, что вы избегаете дорогостоящей стоимости mallo c () / free ()

void *myThread(void *arg)
{
  int *result=malloc(sizeof(int));
  *result=42;
  return result;
}

...
int *res;
pthread_join(tid, &res);
int result=*res; // obtain 42
free(res);

Лучшим решением, чтобы избежать этой стоимости, было бы использование параметра потока функция.

void *myThread(void *arg)
{
  int *result=arg;
  *result=42;
  return NULL;
}

...
int expected_result;
pthread_create(&tid, NULL, myThread, &expected_result);
pthread_join(tid, NULL);
// here expected_result has the value 42
...