exponenta event banner

CERT C: CON34-C правил

Объявление объектов, совместно используемых потоками, с соответствующими сроками хранения

Описание

Определение правила

Объявить объекты, совместно используемые потоками, с соответствующей длительностью хранения [1 ].

Примеры

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

Проблема

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

Риск

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

Например, рассмотрим функцию запуска 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 parentVal = 1;
  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;
}

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

Группа: Правило 14. Параллелизм (CON)
Представлен в R2020a

[1] Данное программное обеспечение было создано компанией MathWorks и включает в себя следующие компоненты: «Веб-сайт SEI CERT-C», © 2017 Университет Карнеги-Меллон, веб-сайт SEI CERT-C + + © 2017 Университет Карнеги-Меллон, "Стандарт кодирования SEI CERT C - Правила разработки безопасных, Надежные и безопасные системы - 2016 Edition ", © 2016 Университет Карнеги-Меллон, и "Стандарт кодирования SEI CERT C++ - Правила разработки безопасных, Надежные и безопасные системы в C++ - 2016 Edition "© 2016 Университет Карнеги-Меллон, со специальным разрешением от его Института программного обеспечения.

ЛЮБОЙ МАТЕРИАЛ УНИВЕРСИТЕТА КАРНЕГИ МЕЛЛОНА И/ИЛИ ЕГО ПРОГРАММНОГО ИНЖЕНЕРНОГО ИНСТИТУТА, СОДЕРЖАЩИЙСЯ В НАСТОЯЩЕМ ДОКУМЕНТЕ, ПОСТАВЛЯЕТСЯ КАК ЕСТЬ. УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, В ОТНОШЕНИИ ЛЮБЫХ ВОПРОСОВ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИИ ПРИГОДНОСТИ ДЛЯ ЦЕЛЕЙ ИЛИ ТОВАРНОЙ ПРИГОДНОСТИ, ИСКЛЮЧИТЕЛЬНОСТИ ИЛИ РЕЗУЛЬТАТОВ, ПОЛУЧЕННЫХ ОТ ИСПОЛЬЗОВАНИЯ УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ В ОТНОШЕНИИ СВОБОДЫ ОТ ПАТЕНТА, ТОВАРНОГО ЗНАКА ИЛИ НАРУШЕНИЯ АВТОРСКИХ ПРАВ.

Данное программное обеспечение и связанная с ним документация не были рассмотрены и не одобрены Университетом Карнеги-Меллона или его Институтом разработки программного обеспечения.