ISO/IEC TS 17961 [fileclose]

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

Описание

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

Не удается закрыть файлы или освободить динамическую память, когда они больше не нужны.[1]

Реализация Polyspace

Эта проверка проверяет на наличие следующих проблем:

  • Утечка памяти.

  • Утечка ресурсов.

  • Утечка памяти для конкретного потока.

Примеры

расширить все

Проблема

Утечка памяти происходит, когда вы не освобождаете блок памяти, выделенный через malloc, calloc, realloc, или new. Если память выделена в функции, дефект не возникает, если:

  • В рамках функции вы освобождаете память, используя free или delete.

  • Функция возвращает указатель, назначенный malloc, calloc, realloc, или new.

  • Функция сохраняет указатель в глобальной переменной или в параметре.

Риск

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

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

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

Чтобы освободить блок памяти, используйте free функция на указателе, который использовался во время выделения памяти. Для образца:

ptr = (int*)malloc(sizeof(int));
//...
free(ptr);

Это хорошая практика - выделять и освобождать память в том же модуле на том же уровне абстракции. Например, в этом примере func выделяет и освобождает память на том же уровне, но func2 не делает.

void func() {
  ptr = (int*)malloc(sizeof(int));
  {
    ...
  }
  free(ptr);
}

void func2() {
  {
   ptr = (int*)malloc(sizeof(int));
   ...
  }
  free(ptr);
}
См. Раздел «Правила CERT-C MEM00-C».

Пример - Динамическая память, не выпущенная до конца функции
#include<stdlib.h>
#include<stdio.h>

void assign_memory(void)
{
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) 
        {
         printf("Memory allocation failed");
         return;
        }


    *pi = 42;
    /* Defect: pi is not freed */
}

В этом примере pi динамически распределяется по malloc. Функция assign_memory не освобождает память и не возвращается pi.

Коррекция - бесплатная память

Одной из возможных коррекций является освобождение памяти, на которую ссылаются pi использование free функция. The free функция должна быть вызвана перед функцией assign_memory заканчивается

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

void assign_memory(void)
{
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) 
        {
         printf("Memory allocation failed");
         return;
        }
    *pi = 42;

    /* Fix: Free the pointer pi*/
    free(pi);                   
}
Коррекция - Возвращаемый указатель из динамического распределения

Еще одна возможная коррекция - вернуть указатель мыши pi. Возврат pi позволяет выполнять вызов функции assign_memory чтобы освободить блок памяти с помощью pi.

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

int* assign_memory(void)
{
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) 
        {
            printf("Memory allocation failed");
            return(pi);
        }
    *pi = 42;

    /* Fix: Return the pointer pi*/
    return(pi);                   
}
Проблема

Утечка ресурсов происходит, когда вы открываете поток файлов при помощи FILE указатель, но не закрывайте его раньше:

  • Конец возможностей указателя.

  • Назначение указателя другому потоку.

Риск

Если вы не отпустите указатели на файлы явным образом как можно скорее, может возникнуть отказ из-за истощения ресурсов.

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

Закройте FILE указатель до конца его возможностей или до назначения указателя другому потоку.

Пример - FILE Указатель не выпущен до конца возможностей
#include <stdio.h>

void func1( void ) {
    FILE *fp1;
    fp1 = fopen ( "data1.txt", "w" );
    fprintf ( fp1, "*" );

    fp1 = fopen ( "data2.txt", "w" );
    fprintf ( fp1, "!" );
    fclose ( fp1 );
}

В этом примере указатель на файл fp1 указывает на файл data1.txt. Перед fp1 явно отделяется от файлового потока data1.txt, он используется для доступа к другому файлу data2.txt.

Коррекция - релиз FILE Указатель

Одной из возможных коррекций является явная диссоциация fp1 из файлового потока data1.txt.

#include <stdio.h>

void func1( void ) {
    FILE *fp1;
    fp1 = fopen ( "data1.txt", "w" );
    fprintf ( fp1, "*" );
    fclose(fp1);

    fp1 = fopen ( "data2.txt", "w" );                  
    fprintf ( fp1, "!" );
    fclose ( fp1 );
}
Проблема

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

Чтобы создать специфичное для потока хранилище, вы обычно делаете следующие шаги:

  1. Вы создаете ключ для специфичного для потока хранилища.

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

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

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

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

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

Шашка работает с этими семействами функций:

  • tss_get и tss_set (C11)

  • pthread_getspecific и pthread_setspecific (POSIX)

Риск

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

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

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

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

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

Пример - Память, не освобожденная в конце потока
#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. Дефект не может возникнуть на практике, потому что раздел обработки ошибок вводится только в случае сбоя динамического выделения памяти. Проигнорируйте этот оставшийся дефект с соответствующими комментариями. Смотрите Адрес Результаты Polyspace через исправления ошибок или обоснования.

#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;
}

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

Решимость: Undecidable
Введенный в R2019a

[1] Выдержки из стандарта «Техническая спецификация ISO/IEC TS 17961 - 2013-11-15» воспроизводятся с согласия АФНОР. Только оригинальный и полный текст стандарта, опубликованный AFNOR Editions - доступный через веб-сайт www.boutique.afnor.org - имеет нормативное значение.