C++ の揮発性修飾子

胡金庫 2023年10月12日
C++ の揮発性修飾子

この記事では、C++ の volatile 修飾子を紹介します。

volatile 修飾子を使用して、C++ の別のスレッドまたは外部アクションによって変更されるオブジェクトを示する

volatile キーワードの動作は、一般にハードウェアに依存すると見なす必要があります。ユーザースペースアプリケーションの開発者は、さまざまなシナリオで修飾子がどのように解釈されるかについて、特定のコンパイラマニュアルを常に参照する必要があります。通常、volatile キーワードは、指定されたオブジェクトをロード操作用に最適化してはならず、常にレジスタまたはキャッシュではなくメインメモリから取得する必要があることをコンパイラに通知します。ほとんどの場合ソフトウェアにアクセスできず、ハードウェアでのみ管理されるキャッシュの階層が複数あることに注意してください。ただし、コンパイラがレジスタ内のメモリ位置を読み込もうとすると、自動的にキャッシュされます。したがって、同じメモリ位置への結果としてのアクセスは、CPU に近いキャッシュラインから、RAM よりも数倍高速に実行できます。

一方、オブジェクトが何らかの外部信号または割り込みのようなルーチンによって変更された場合、キャッシュされた値は無効になるため、変更された値は RAM からアクセスする必要があります。したがって、volatile オブジェクトへのアクセスは、それに応じてコンパイラによって処理されます。考えられるシナリオを示すために、グローバルな volatile 整数変数を変更する関数と、while ループステートメントで同じ整数を評価する別の関数を実装します。この例が機能するためには、while ループの本体が空である可能性があることに注意してください。最初に、main 関数は IncrementSeconds 関数を実行する別のスレッドを作成します。その直後、メインスレッドは DelayTenSeconds 関数を呼び出します。この関数は、seconds 変数が 10 の値を超えない場合に戻らないループに入ります。他のスレッドがすでに同時に seconds 変数のインクリメントを開始したため、メインスレッドはすぐに変更された値を監視し、関数から戻ります。

#include <unistd.h>

#include <iostream>
#include <thread>

using std::cerr;
using std::cin;
using std::cout;
using std::endl;

volatile int seconds = 0;

void DelayTenSeconds() {
  while (seconds < 10) {
    usleep(500000);
    cerr << "waiting..." << endl;
  }
}

void IncrementSeconds() {
  for (int i = 0; i < 10; ++i) {
    sleep(1);
    cerr << "incremented " << endl;
    seconds = seconds + 1;
  }
}

int main() {
  struct timeval start {};
  struct timeval end {};
  std::thread th1;

  th1 = std::thread(IncrementSeconds);

  DelayTenSeconds();

  th1.join();
  return EXIT_SUCCESS;
}

出力:

waiting...
incremented
waiting...
....
waiting...
10.002481 sec

その結果、基本的に、volatile オブジェクトが外部アクションによって変更されるまで待機する条件付き遅延関数を実装しました。ここで、volatile 修飾子が削除され、通常のグローバル変数が使用されている場合、このコードはまったく同じように動作することに気付くかもしれませんが、変更コードブロックが別の変換ユニットまたは外部信号アクションからのものである可能性があることを忘れないでください。後者のシナリオでは、変数が現在の変換単位で変更されないため、コンパイラーは DelayTenSeconds ループを最適化する必要があります。

#include <sys/time.h>
#include <unistd.h>

#include <iostream>
#include <thread>

using std::cerr;
using std::cin;
using std::cout;
using std::endl;

volatile int seconds = 0;

void DelayTenSeconds() {
  while (seconds < 10) {
    usleep(500000);
    cerr << "waiting..." << endl;
  }
}

float TimeDiff(struct timeval *start, struct timeval *end) {
  return (end->tv_sec - start->tv_sec) + 1e-6 * (end->tv_usec - start->tv_usec);
}

void IncrementSeconds() {
  for (int i = 0; i < 10; ++i) {
    sleep(1);
    cerr << "incremented " << endl;
    seconds = seconds + 1;
  }
}

int main() {
  struct timeval start {};
  struct timeval end {};
  std::thread th1;

  th1 = std::thread(IncrementSeconds);

  gettimeofday(&start, nullptr);
  DelayTenSeconds();
  gettimeofday(&end, nullptr);

  printf("%0.6f sec\n", TimeDiff(&start, &end));

  th1.join();
  return EXIT_SUCCESS;
}
著者: 胡金庫
胡金庫 avatar 胡金庫 avatar

DelftStack.comの創設者です。Jinku はロボティクスと自動車産業で8年以上働いています。自動テスト、リモートサーバーからのデータ収集、耐久テストからのレポート作成が必要となったとき、彼はコーディングスキルを磨きました。彼は電気/電子工学のバックグラウンドを持っていますが、組み込みエレクトロニクス、組み込みプログラミング、フロントエンド/バックエンドプログラミングへの関心を広げています。

LinkedIn Facebook