メインコンテンツ

ISO/IEC TS 17961 [fileclose]

Failing to close files or free dynamic memory when they are no longer needed

説明

ルール定義

不要になったファイルを閉じない、または不要になった動的メモリを解放しない。1

Polyspace 実装

このチェッカーは以下の問題をチェックします。

  • メモリ リーク

  • リソース リーク

  • スレッド固有のメモリ リーク

すべて展開する

問題

メモリ リークは、malloccallocrealloc または new を通して割り当てられたメモリのブロックが解放されない場合に発生します。メモリが関数内で割り当てられる場合、以下の条件を満たせば欠陥は生じません。

  • 関数内で、free または delete を使用してメモリが解放される。

  • 関数が malloccallocrealloc または 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 Rule 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 */
}

この例では、malloc によって pi が動的に割り当てられています。関数 assign_memory はメモリを解放せず、pi も返しません。

修正 — メモリを解放

1 つの修正方法として、pi が参照するメモリを関数 free を使用して解放することができます。関数 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);                   
}
修正 — 動的割り当てからポインターを返す

もう 1 つの修正方法は、ポインター 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 ポインターを解放

1 つの修正方法として、fp1data1.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 は欠陥にフラグを設定します。適切なコメントを使用してこの欠陥は無視します。詳細は、以下を参照してください。

例 - スレッドの終了時にメモリが未解放
#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 で次の 2 つの関数を呼び出しています。

  • add_data: この関数はストレージを動的に割り当て、関数 tss_set を使用してそのストレージとキーを関連付けます。

  • print_data: この関数は関数 tss_get を使用して、格納されているデータを読み取ります。

func が戻った時点では、動的に割り当てられたストレージが解放されていません。

修正 — 動的に割り当てられたメモリを明示的に開放

1 つの修正方法として、スレッドの開始関数が終わる前に、動的に割り当てられたメモリを明示的に開放します。訂正されたバージョンでは変更が強調表示されています。

この訂正されたバージョンでも、func のエラー処理セクションの return ステートメントには相変わらず欠陥が表示されます。エラー処理セクションに入るのは動的なメモリ割り当てが失敗した場合に限られるため、この欠陥は実際には発生しません。適切なコメントを使用してこの残りの欠陥は無視します。詳細は、以下を参照してください。

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

チェック情報

決定可能性:決定不可能

バージョン履歴

R2019a で導入


1 Extracts from the standard "ISO/IEC TS 17961 Technical Specification - 2013-11-15" are reproduced with the agreement of AFNOR. Only the original and complete text of the standard, as published by AFNOR Editions - accessible via the website www.boutique.afnor.org - has normative value.