web-dev-qa-db-ja.com

別のモジュールからアクセスした場合でも、遅延タイマー変数を揮発性として宣言する必要がありますか

これは、変数の書き込み/読み取りをキャッシュすることよりも、最適化を防ぐためにvolatileを使用することに関する質問です。特に、タイマー遅延変数は、すべてを揮発的に宣言したくないので、そうすることで最適化を無駄にしたくありません。

まず、揮発性を宣言しない限り、最適化がオンになっていると次のスニペットが機能しないことを知っています。

int i;
for(i=0; i<LARGE_NUM; i++); //Delay for a bit

ループが代わりに関数呼び出しを呼び出し、それを待つ場合、コンパイラーはソースファイルを調べて呼び出しを最適化しますか?一部のコンパイラはマルチファイルコンパイルを提供しているため、これを求めています。

//A.c
#include "B.h"    //B.h has declaration for dec(int)

...
int i;
for(i=LARGE_NUM; i>0; i=dec(i));
...

//B.c

int dec(int a){
  return a-1;
}

もう少し複雑で、ハードウェア割り込みが関係するものはどうですか?

//A.c
#include "B.h"

...
timer_start(LARGE_NUM);
while(timer_busy());
...

void hardware_timer_isr(void){    //Hardware timer interrupt
    timer_tick();
}

//B.c

static int time=0;

void timer_start(int t){
    time = t;
}

int timer_busy(void){
    return time>0;
}

void timer_tick(void){
    if(time > 0)
        time--;
}

変数を揮発性として宣言する必要がありますか?それとも特に気をつけるべきことはありますか?

4
Pyxzure

コンパイラーは as-ifルール の下で動作し、プログラムの監視可能な動作を変更しないコード変換をすべて許可します。

[C++ 14:1.5/8]

適合実装の最小要件は次のとおりです。

  • 揮発性オブジェクトへのアクセスは、抽象マシンのルールに従って厳密に評価されます。
  • プログラムの終了時に、ファイルに書き込まれたすべてのデータは、抽象的なセマンティクスに従ってプログラムを実行した場合に生じる可能性のある結果の1つと同じになります。
  • 対話型デバイスの入力と出力のダイナミクスは、プログラムが入力を待つ前に、プロンプト出力が実際に配信されるような方法で行われるものとします。インタラクティブデバイスを構成するものは、実装によって定義されます。

これらはまとめて、プログラムの観察可能な動作と呼ばれます。

[C11 5.1.2.3.6プログラムの実行]にも同様の表現があります。

適合実装の最小要件は次のとおりです。

  • 揮発性オブジェクトへのアクセスは、抽象マシンのルールに従って厳密に評価されます。
  • プログラムの終了時に、ファイルに書き込まれるすべてのデータは、抽象的なセマンティクスに従ってプログラムを実行した場合の結果と同じになります。
  • 対話型デバイスの入力と出力のダイナミクスは、7.21.3で指定されたとおりに発生するものとします。これらの要件の意図は、プログラムが入力を待機する前にプロンプ​​トメッセージが実際に表示されるように、バッファなしまたはラインバッファ付きの出力をできるだけ早く表示することです。

これは、プログラムの観察可能な動作です。

遅延は観察可能な動作とは見なされず、最初の例は空のプログラムに「最適化」できます。

空のループの削除などのコンパイラー変換を許可するために(終了が証明できない場合でも)、C++ 14標準では次のように述べられています。

[C++ 14:1.10/24]

実装は、すべてのスレッドが最終的に次のいずれかを実行すると想定する場合があります。

  • 終了、
  • ライブラリI/O関数を呼び出します。
  • 揮発性オブジェクトにアクセスまたは変更する、または
  • 同期操作またはアトミック操作を実行します。

[注:これは、終了が証明できない場合でも、空のループの削除などのコンパイラー変換を可能にすることを目的としています。 —エンドノート]

C/C++は最小実行時間の保証を提供していますか? も参照)


2番目の例は、通常、外部ライブラリのコードを分析して、I/Oまたは揮発性アクセスを実行するかどうかを判断することができないため、コンパイラにとってより困難です。ただし、静的にリンクされたサードパーティのライブラリコードは、 リンク時の最適化 の対象となる可能性があるため、マルチファイル構成は乗り越えられない障壁ではありません。


3番目の例では、新しいことは何も紹介されていません。

  1. 割り込みベクトルエントリはプログラムの起動時に初期化されます
  2. ハンドラー関数のアドレスを取得する必要があり、_hardware_timer_isr_が最適化されないように保護することで十分です
  3. ただし、time変数は、割り込みハンドラーによって変更できる変数として手動で指定されていないため、次のようになります。

    _ timer_start(LARGE_NUM);
     while(timer_busy());
    _

    監視可能な動作がなく、最適化することができます(詳細については、「 コンパイラが割り込みコードを最適化しないのはなぜですか? 」を参照してください)。


遅延が必要な場合は、 _std::sleep_for_ または _std::sleep_until_ を使用できます。


その他の注意事項

意図的にwhile(1);または同様に難読化された無限ループを入れて、プログラムを意図的に停止した場合はどうなりますか?上記のC++ 14 1.10/24によると、終了を証明できない場合でも、ループ自体はプログラムの監視可能な動作を変更しないため、合法的に削除できます。

メモ( "これは、終了が証明できない場合でも、空のループの削除などのコンパイラ変換を可能にすることを目的としています")理由は 無限を検出する方法がないためですループは普遍的に および終了を証明できないとコンパイラーが妨害され、そうでなければ有用な変換が行われる可能性があります( N1528に良い例があります:無限ループの未定義の動作はなぜですか? )。

C++言語の場合、状況は次のとおりです。

C言語の場合 C11は、定数式である式を制御するための例外を提供します

3
manlio