Automatic or thread local variable escaping from a thread

Переменная передается от одного потока до другого, не гарантируя, что переменная остается в живых через длительность последнего потока

Описание

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

Дефектное средство проверки применяется и к C11 и к потокам POSIX®.

Риск

Автоматическая локальная переменная или локальная переменная потока выделяются на стеке в начале потока, и его время жизни расширяет до конца потока. Переменная, как гарантируют, не будет жива, когда различный поток получит доступ к ней.

Например, рассмотрите функцию запуска потока C11 с этими линиями:

int start_thread(thrd_t *tid) {
   int aVar = 0;
   if(thrd_success != thrd_create(tid, start_thread_child, &aVar) {
     ...
   }
}

thrd_create функция создает дочерний поток с функцией запуска start_thread_child и передает адрес автоматической переменной aVar к этой функции. Когда этот дочерний поток доступы aVar, родительский поток может завершить выполнение и aVar больше не находится на стеке. Доступ может привести к чтению непредсказуемых значений.

Фиксация

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

  • Объявите переменную static так, чтобы это не выходило из стека, когда текущий поток завершает выполнение.

  • Динамически выделите устройство хранения данных для переменной так, чтобы это было выделено на куче вместо стека и должно было быть явным образом освобождено. Убедитесь, что освобождение происходит после обоих потоков полное выполнение.

Эти решения требуют, чтобы вы создали переменную в нелокальной памяти. Вместо этого можно использовать другие решения, такие как shared ключевое слово с интерфейсом поточной обработки OpenMP, который позволяет вам безопасно совместно использовать локальные переменные через потоки.

Примеры

развернуть все

#include <threads.h>
#include <stdio.h>

int create_child_thread(void *childVal) {
  int *res = (int *)childVal;
  printf("Result: %d\n", *res);
  return 0;
}

void create_parent_thread(thrd_t *tid, int *parentPtr) {
   if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
    /* Handle error */
  }
}

int main(void) {
  thrd_t tid;
  int parentVal = 1;
  
  create_parent_thread(&tid, &parentVal);


  if (thrd_success != thrd_join(tid, NULL)) {
    /* Handle error */
  }
  return 0;
}

В этом примере, значение parentVal локально для родительского потока, который запускается в main и продолжается в функциональный create_parent_thread. Однако в теле create_parent_thread, адрес этой локальной переменной передается дочернему потоку (поток со стандартной программой запуска create_child_thread). Родительский поток может завершить выполнение и переменную parentVal может выйти из осциллографа, когда дочерний поток получает доступ к этой переменной.

Та же проблема появляется, если переменная объявляется как локальная для потока, например, с ключевым словом C11 _Thread_local (или thread_local):

_Thread_local int parentVal = 1;

Коррекция – использует статические переменные

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

#include <threads.h>
#include <stdio.h>

int create_child_thread(void *childVal) {
  int *res = (int *)childVal;
  printf("Result: %d\n", *res);
  return 0;
}

void create_parent_thread(thrd_t *tid, int *parentPtr) {
   if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
    /* Handle error */
  }
}

int main(void) {
  thrd_t tid;
  static int parentVal = 1;
  
  create_parent_thread(&tid, &parentVal);


  if (thrd_success != thrd_join(tid, NULL)) {
    /* Handle error */
  }
  return 0;
}
Коррекция – использует динамическое выделение памяти

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

#include <threads.h>
#include <stdio.h>

int create_child_thread(void *childVal) {
  int *res = (int *)childVal;
  printf("Result: %d\n", *res);
  return 0;
}

void create_parent_thread(thrd_t *tid, int *parentPtr) {
   if (thrd_success != thrd_create(tid, create_child_thread, parentPtr)) {
    /* Handle error */
  }
}

int main(void) {
  thrd_t tid;
  int parentPtr = (int*) malloc(sizeof(int));
  
  if(parentPtr) {
      create_parent_thread(&tid, parentPtr);
     

      if (thrd_success != thrd_join(tid, NULL)) {
        /* Handle error */
      }
      free(parentPtr);
  }
  return 0;
}

Проверяйте информацию

Группа: параллелизм
Язык: C | C++
Значение по умолчанию: Off
Синтаксис командной строки: LOCAL_ADDR_ESCAPE_THREAD
Удар: носитель
Введенный в R2020a