передайте (void*) 100 в качестве аргумента

#c

Вопрос:

Я только что видел пример, который мне кажется странным, из книги https://pages.cs.wisc.edu/~ремзи/ОСТЕП/темы-api.pdf :

Во-вторых, если мы просто передаем одно значение (например, a long long int ), нам не нужно упаковывать его в качестве аргумента …

 void *mythread(void *arg) {
    long long int value = (long long int) arg; 
    printf("%lldn", value);
    return (void *) (value   1);
}
int main(int argc, char *argv[]) {
    pthread_t p;
    long long int rvalue;
    Pthread_create(amp;p, NULL, mythread, (void *) 100);
    Pthread_join(p, (void **) amp;rvalue);
    printf("returned %lldn", rvalue);
    return 0;
}
 
  1. arg это указатель, почему long long int value = (long long int) arg; он правильный? Почему приведение не требуется от типа указателя к типу без указателя?
    Разве это не должно быть: long long int value = *((long long int*)(arg)); ?
  2. Это return (void *) (value 1); правильно ? Чем это отличается от неправильной практики: «возвращать указатель локальной переменной» ?

Комментарии:

1. long long int value = (long long int) arg; это неверно. Это может привести к определенному поведению реализаций, включая повышение сигнала. Используется uintptr_t для хранения указателей в целочисленных типах.

2. return (void *) (value 1); может вызвать УБ. Это вполне возможно value==LLONG_MAX . Снова используйте uintptr_t , чтобы избежать этой проблемы. Допускается, чтобы указатель указывал мимо допустимого объекта, поэтому 1 это нормально и не должно вызывать UB или обертывание при использовании uintptr_t .

3. в стандартном C это неопределенное поведение (если (void *)100 только оно не указывает на реальный объект), но в любом случае оно несколько распространено в программировании unix

4. Если это на самом деле из книги, то вам нужно немедленно избавиться от этой книги. Если они даже не могут разобраться с функциями pthread, то вы знаете, что код никогда не был протестирован, и книга никогда не была корректно прочитана…

Ответ №1:

Техника , описанная в книге, срабатывает undefined behavior и работает только в том случае, если sizeof(void*) == sizeof(long long int) , что часто неверно (например: на 32-bit x86 и ARM32v7 платформах).

По сути, это ярлык, который хранит значение long long целого числа, как если бы это был адрес указателя (и поэтому он хранится как void* ).

Я настоятельно рекомендую никогда не использовать этот ярлык, а просто передать указатель на long long переменную, как обычно:

 void* mythread(void* arg) {
  long long real_arg = *((long long*) arg);
  ...
  long long* result = malloc(sizeof(long long));
  if (result == NULL) { return NULL; }
  *result = 123LL;
  return resu<
}

int main(void) {
  ...
  long long value = 53LL;
  pthread_create(amp;p, NULL, mythread, amp;value);
  ...
  void* resu<
  pthread_join(p, amp;result);
  long long* real_result = resu<
  ...
  free(result);
  return 0;
}
 

РЕДАКТИРОВАТЬ: просто примечание: динамического распределения памяти, используемого в предыдущем фрагменте кода для возврата long long результата, можно избежать, используя другие подходы. Например, вы можете повторно arg использовать указатель, чтобы также сохранить результат:

 void* mythread(void* arg) {
  long long* real_arg = arg;
  printf("I got: %lldn", *real_arg); // I got: 53
  ...
  *real_arg = 123LL;
  return real_arg;
}

int main(void) {
  ...
  long long value = 53LL;
  pthread_create(amp;p, NULL, mythread, amp;value);
  ...
  void* resu<
  pthread_join(p, amp;result);
  long long* real_result = resu<
  printf("Result: %lldn", *real_result); // Result: 123
  ...
  return 0;
}
 

Комментарии:

1. Ваш код не делает то же самое. Вы разыменовываете указатель mythread() , он этого не делает.

2. @1243123412341212121212123 Да, конечно, код, который я опубликовал, является правильной альтернативой тому, что делает код операции.

3. @124312341234123412341223 в этом весь смысл ответа. 😉

4. @LucaPolito Не уверен, что это то, чего хочет ОП. Если вы используете uintptr_t , вы можете исправить ответ, не изменяя поведение на платформах, где sizeof(void*) == sizeof(long long) .

5. Да, теперь это правильно. Поскольку поток разыменовывает адреса автоматических переменных main , важно main , чтобы эти переменные никогда не изменялись между вызовами pthread_create и pthread_join и чтобы основной поток не выходил до создания потока.

Ответ №2:

arg-это указатель, почему значение long long int = (long long int) arg; правильно?
Является ли return (void *) (значение 1); правильно ?

Это не так. Передача целочисленных значений через поток void* pthread является обычным, но грязным взломом. Все это при условии, что void* и используемый целочисленный тип имеют одинаковый размер. Более правильным целочисленным типом для использования был intptr_t бы тот, который в отличие long long от гарантированно имеет тот же размер, что и a void* .

Помимо размера, преобразование между целыми числами и указателями определяется реализацией: в некоторых системах это будет работать просто отлично, в других системах может возникнуть ловушка, если переданное целое число будет интерпретировано как несоосный адрес. Как правило, вы увидите такого рода хаки в коде Linux, который никогда не будет перенесен куда-либо еще, потому что ни x86, ни ARM не попадут в ловушку.

Что касается того, как это правильно написать… ну, вы можете передать адрес локальной переменной в потоке «создатель потока». Недостатком этого является то, что в случае, если эта переменная выходит за рамки «создателя потока», она больше не действительна. Обходной путь для этого состоит в том, чтобы использовать static , а затем сделать локальную копию внутри потока первым делом:

 long long value = ...;
pthread_create(amp;p, NULL, mythread, amp;value);

...

void *mythread(void *arg) 
{
  long long value = *(long long*)arg;
  ...
}
 

Однако вам все равно нужно быть осторожным, если несколько потоков используют одну и ту же переменную. Другим вариантом является динамическое выделение, но, как правило, оно выполняется медленнее.

В целом узким местом в этом коде является само создание потока, поэтому выполнять предварительную оптимизацию, подобную этому взлому, чтобы сохранить несколько инструкций, нецелесообразно.

Комментарии:

1. Здесь задействован не только C, но и POSIX. Кто-нибудь знает, гарантирует ли POSIX что-либо в указателях, что было бы уместно?