exponenta event banner

Утечка памяти в зависимости от потока

Динамически выделенная память для определенного потока не освобождается до конца потока

Описание

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

Для создания системы хранения данных, зависящей от потока, обычно выполняются следующие действия:

  1. Создается ключ для хранения данных, специфичных для потоков.

  2. Вы создаете нити.

  3. В каждом потоке вы выделяете хранилище динамически, а затем связываете ключ с этим хранилищем.

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

  4. Перед окончанием потока освободите специфическую для потока память с помощью клавиши.

Средство проверки помечает пути выполнения в потоке, где отсутствует последний шаг.

Средство проверки работает с этими семействами функций:

  • tss_get и tss_set (C11)

  • pthread_getspecific и pthread_setspecific (POSIX)

Риск

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

Зафиксировать

Освободить динамически выделяемую память до конца потока.

Можно явно освободить динамически выделенную память с помощью таких функций, как free.

Кроме того, при создании ключа можно связать функцию деструктора с ключом. Функция деструктора вызывается со значением ключа в качестве аргумента в конце потока. В теле функции деструктора можно освободить любую память, связанную с ключом. При использовании этого метода функция поиска ошибок по-прежнему помечает дефект. Игнорировать этот дефект с соответствующими комментариями. См. раздел Результаты анализа пространства адресов с помощью исправлений ошибок или обоснований.

Примеры

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

#include <threads.h>
#include <stdlib.h>
 
/* Global key to the thread-specific storage */
tss_t key;
enum { MAX_THREADS = 3 };
 

int add_data(void) {
  int *data = (int *)malloc(2 * sizeof(int));
  if (data == NULL) {
    return -1;  /* Report error  */
  }
  data[0] = 0;
  data[1] = 1;
 
  if (thrd_success != tss_set(key, (void *)data)) {
    /* Handle error */
  }
  return 0;
}
 
void print_data(void) {
  /* Get this thread's global data from key */
  int *data = tss_get(key);
 
  if (data != NULL) {
    /* Print data */
  }
}
 
int func(void *dummy) {
  if (add_data() != 0) {
    return -1;  /* Report error */
  }
  print_data();
  return 0;
}
 
int main(void) {
  thrd_t thread_id[MAX_THREADS];
 
  /* Create the key before creating the threads */
  if (thrd_success != tss_create(&key, NULL)) {
    /* Handle error */
  }
 
  /* Create threads that would store specific storage */
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_create(&thread_id[i], func, NULL)) {
      /* Handle error */
    }
  }
 
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_join(thread_id[i], NULL)) {
      /* Handle error */
    }
  }
 
  tss_delete(key);
  return 0;
}

В этом примере функция запуска каждого потока func вызывает две функции:

  • add_data: Эта функция динамически распределяет ресурсы хранения и связывает их с ключом с помощью tss_set функция.

  • print_data: Эта функция считывает сохраненные данные с помощью tss_get функция.

В точках, где func возвращает, динамически выделенное хранилище не освобождено.

Исправление - свободная динамически выделяемая память явно

Одной из возможных корректировок является явное освобождение динамически выделяемой памяти перед выходом из функции запуска потока. См. выделенные изменения в исправленной версии.

В этой исправленной версии дефект все еще появляется на return оператор в разделе обработки ошибок func. Дефект не может возникнуть на практике, поскольку раздел обработки ошибок вводится только в случае сбоя динамического выделения памяти. Проигнорируйте этот оставшийся дефект с соответствующими комментариями. См. раздел Результаты анализа пространства адресов с помощью исправлений ошибок или обоснований.

#include <threads.h>
#include <stdlib.h>
 
/* Global key to the thread-specific storage */
tss_t key;
enum { MAX_THREADS = 3 };
 

int add_data(void) {
  int *data = (int *)malloc(2 * sizeof(int));
  if (data == NULL) {
    return -1;  /* Report error  */
  }
  data[0] = 0;
  data[1] = 1;
 
  if (thrd_success != tss_set(key, (void *)data)) {
    /* Handle error */
  }
  return 0;
}
 
void print_data(void) {
  /* Get this thread's global data from key */
  int *data = tss_get(key);
 
  if (data != NULL) {
    /* Print data */
  }
}
 
int func(void *dummy) {
  if (add_data() != 0) {
    return -1;  /* Report error */
  }
  print_data();
  free(tss_get(key));
  return 0;
}
 
int main(void) {
  thrd_t thread_id[MAX_THREADS];
 
  /* Create the key before creating the threads */
  if (thrd_success != tss_create(&key, NULL)) {
    /* Handle error */
  }
 
  /* Create threads that would store specific storage */
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_create(&thread_id[i], func, NULL)) {
      /* Handle error */
    }
  }
 
  for (size_t i = 0; i < MAX_THREADS; i++) {
    if (thrd_success != thrd_join(thread_id[i], NULL)) {
      /* Handle error */
    }
  }
 
  tss_delete(key);
  return 0;
}

Информация о результатах

Группа: Параллелизм
Язык: C | C++
По умолчанию: Откл.
Синтаксис командной строки: THREAD_MEM_LEAK
Воздействие: среднее
CWE ID: 401, 404
Представлен в R2018b