メインコンテンツ

スレッドからエスケープする自動変数またはスレッド ローカル変数

変数が後続スレッドの持続期間を通して有効であることが確認されないままスレッド間で渡される

説明

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください

この欠陥は、自動またはスレッド ローカル変数が後続スレッドの持続期間を通して有効であることが確認されないままスレッド間でアドレスによって渡された場合に発生します。

欠陥チェッカーは、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 を宣言して、現在のスレッドが実行を完了したときにスタックから削除されないようにします。

  • スタックではなくヒープ上に割り当てられ、明示的に割り当て解除する必要があるように、変数用のストレージを動的に割り当てます。両方のスレッドが実行を完了してから割り当て解除が実行されることを確認します。

これらの解決策では、非ローカル メモリで変数を作成する必要があります。代わりに、スレッド全体でローカル変数を安全に共有できる OpenMP のスレッド インターフェイスを使用した shared キーワードなどの他の解決策も使用できます。

すべて展開する

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

この例では、値 parentValmain で始まり、関数 create_parent_thread まで続く親スレッドに対してローカルです。ただし、create_parent_thread の本体では、このローカル変数のアドレスが子スレッド (ルーチン create_child_thread を開始するスレッド) に渡されます。親スレッドは、実行を完了しており、変数 parentVal が子スレッドからアクセスされたときにはスコープから除外されている可能性があります。

変数が C11 キーワード _Thread_local (または thread_local) などを使用してスレッド ローカルとして宣言された場合にも同じ問題が発生します。

_Thread_local int parentVal = 1;

修正 – 静的変数を使用

1 つの修正方法として、変数 parentValstatic を宣言して、それらの変数がプログラムの存続期間を通してスタック上に存在するようにします。

#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;
}
修正 – 動的メモリ割り当てを使用

1 つの修正方法として、スレッド全体で共有する変数用のストレージを動的に割り当て、変数が必要なくなったら明示的にストレージを解放します。

#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++
既定値: オフ
コマンド ライン構文: LOCAL_ADDR_ESCAPE_THREAD
影響度: Medium

バージョン履歴

R2020a で導入