web-dev-qa-db-ja.com

未定義の揮発性オブジェクトへの不揮発性アクセスの利点は?

これは、次の文を含むISOCに関する質問です。

不揮発性修飾型の左辺値を使用して、揮発性修飾型で定義されたオブジェクトを参照しようとした場合、動作は未定義です。

ISO9899:1999(C99)では、これは6.7.3型修飾子に表示されます。

まず、この文をテキストから完全に削除した場合でも、動作は未定義ですか?つまり、これはre-statingの既存の要件の欠如ですか、それとも他の場所から推測できる要件を明示的に取り除いているのですか?本文中? (「タイプintの2つの値が加算され、その算術合計が42である場合、動作は未定義です」という文が追加されたと想像してください。そのような文がなければ、そのような追加の要件は推定できる(そして推測できる)値を生成します)。

第二に、これにより実装が提供できる有用な動作は何ですか?つまり、実装は、volatile以外の左辺値でvolatileにアクセスするプログラムを壊し、したがって上記の文に依存する何か有益なことを行うことができますか?それが何であれ、これを行う実装はありますか?

たとえば、Cプログラムがconst以外の左辺値を持つconst修飾オブジェクトにアクセスでき、それらに格納できないことは、興味深く注目に値します。ストアの動作を未定義にすることは、明白に有益です。実装では、定数を読み取り専用ストレージに配置でき、場合によっては、変換時に、マニフェスト定数であるかのようにプログラムイメージを介して定数を伝播できます。それでも、機能するにはconst以外のアクセスが必要です。 constオブジェクトへの非constアクセスを機能させる必要がないことは、おそらく、メリットをもたらさない許容性です。ただし、volatileの場合は、厳密です。volatile以外の資格のあるアクセスは必要ありません。メリットは何ですか?

Cプログラム自体によってvolatileとして定義されているオブジェクトに、非volatile左辺値を介したそのオブジェクトへのアクセスが発生した場合に中断する、便利なプロパティを付与できますか?

(「有用」の下で、私は特に診断を除外したいと思います。メッセージなしで、または「不揮発性としてアクセスされた揮発性」のようなメッセージでそのようなアクセスを検出したときにプログラムを終了することは有用とは見なされません。要件の欠如を強制するため。)

(また、メモリマップドレジスタなどには興味がありません。静的、動的、または自動ストレージでプログラムによって厳密に定義されたオブジェクトです。ハードウェアレジスタの使用は本質的に移植性がありません。)

私が考えることができるのは、volatileオブジェクトへの非volatileアクセスは、(適切には、volatileを介して)最後に格納された値ではなく、古い値を取得する可能性があるということです。左辺値)。これは、次の点で経済的なメリットです。たとえば、volatile int左辺値を介して書き込みが発生した場合、コンパイラは、これがプレーンなint左辺値に影響を与えることを疑う必要はありません。これにより、同じタイプのvolatileオブジェクトと非volatileオブジェクトの混合で機能する関数でのコード生成が向上します。

volatile int global;

void foo(int *p)
{
   int x = *p, y;
   global++; 
   y = *p;
}

ここで、コンパイラは、*pglobalのエイリアスである可能性があることを考慮する必要はありません。これは、その使用法がISO Cに従って機能する必要がないためです。したがって、 *pは、2つの参照間で変更されません。 xyはどちらも、*pへの1回のアクセスから派生した同じ値を受け取ります。

エイリアシングが許可されている場合、コンパイラは、globalに格納されている最新の値を取得するために、2回目のアクセスで*pを再ロードするコードを生成する必要があります。

(上記のプログラムフラグメントからvolatileを削除した場合、これは事実です。確認されていない場合は、少なくとも疑われる必要があります。*pglobalなど*pを再検査する必要があります。そうでない場合は、pの実行時の値をglobalのアドレスと比較して取得するコードを生成する必要があります。 2つの異なるパス。)

ただし、この種のあいまいな最適化の利点を得るために、プログラムにvolatileを導入することはありません。最適化を無効にするために使用されます。代わりにrestrictキーワードを使用して、実装が左辺値がエイリアスされていないことを推論できるようにすることができます。上記は理論的根拠にはなり得ないようです。

5
Kaz

「ポータブルC」のみに関心がある場合は、キーワードvolatileがほとんどまたはまったく役に立たないことがわかるため、要件は異常に思えます。この言語の全体的な目的は、コンパイラがメモリマップされたハードウェアを公開できるようにすることです。 [ポータブルではない]スレッドライブラリをミックスに追加すると、最小限の寿命が得られますが、基本的には、実装固有の動作を実行することが仕事です。仕様に次のように記載されているほどです。

揮発性修飾型を持つオブジェクトは、実装に未知の方法で変更されたり、その他の未知の副作用がある可能性があります。 ...揮発性修飾型を持つオブジェクトへのアクセスを構成するのは、実装定義です。 (ISO/IEC 9899:TC2 6.7.3.6)

不揮発性手段を介して揮発性オブジェクトへのアクセスを拒否することの価値は、その行で明らかになります。この仕様では、「揮発性」操作を「不揮発性」操作とは異なる方法で実装することが許可されています。簡単な例として、揮発性書き込みはマッピング内のマップされたレジスタを検索し、代わりにレジスタに書き込みますが、不揮発性書き込みは単にそのメモリに書き込みます。その例は少し非現実的かもしれませんが、Cの開発者は、多くのプラットフォームでCでソフトウェアを作成することをサポートするために多大な労力を費やし、仕様に1行を追加する以外は(あなたが言及したもの)、文字通り追加する費用はかかりません。言語へのこのサポート。そうすることで、あらゆる種類のエキゾチックなメモリマッピングの状況がはるかに簡単になります。

最新のx86マシンでは、コンパイラがこれを利用する理由はおそらくありません。実際、この場合のx86コンパイラの「未定義の振る舞い」が、実行すべきだと思うことを実行するだけであっても、私は驚かないでしょう。ただし、より貧血のプラットフォームでは、開発者が不揮発性の左辺値を介して揮発性にアクセスする場合を心配する必要がないという機能は、コンパイラコード生成の大きな違いになる可能性があります。

1
Cort Ammon

Loreheadへのコメントで、Kazは、このルールは、コードが明確に定義されている場合にのみ関連すると正しく述べています。コードを作成することはできませんmore結局のところ未定義です。UBはバイナリ状態です。

したがって、揮発性をポータブルに使用します。つまり、シグナルとlongjmpを使用します。どちらの場合も、コンパイラーは適切なセマンティクスが尊重されていることを確認する必要があります。特に、可視(揮発性)グローバル状態の更新は、コードで説明されているとおりにする必要があります。不揮発性状態は任意の順序で更新できます。

ここで、int*を介した書き込みが揮発性状態に影響を与える可能性があると仮定しましょう。これは、間接書き込みを並べ替えることができなくなったことを意味します。これはパフォーマンスに大きな影響を与えます。さらに悪いことに、int*を介したすべての読み取りを実行する必要があり、レジスタにキャッシュすることはできません。

したがって、このルールでは、並べ替え可能なメモリ操作の数を大幅に増やすことで、大幅な最適化が可能になります。

1
MSalters

volatileキーワードは、さまざまなコンパイラでさまざまなことを意味しますが、それらのより重要な用途の1つは、特別なメモリマップドハードウェアを表すことです。したがって、その点で未定義の動作を宣言すると、コンパイラはその動作を使用してそのハードウェアを表すことができます。負の符号付き整数の右シフト、除算、およびモジュラスが正確に指定されていないのと同じ理由です。これにより、各CPUのネイティブ命令セットが機能します。正確に定義された動作が必要な場合は、アトミックとdiv()があります。

1
Davislor

魔法のメモリ位置0xc0103が書き込まれたときに、マシンシャーシの点滅灯の明るさを保存された値に設定する架空のマシンを想像してみてください。

あなたはこのようなプログラムを持っているかもしれません。

volatile uint32_t * pindicator = (volaitle uint32_t *) 0xc0103;

for (;;)
  {
    uint32_t amount = do_some_work();
    *pindicator = amount;
  }

システムが現在処理している作業量に応じて、ライトが点滅します。プログラムが*pindicatorの値をreadsしない場合でも、コンパイラはvolatileとして修飾されているため、ストアを最適化することはできません。コンパイラーは、*pindicatorへのストアがどのような結果をもたらすかを知りませんが、コンパイラーが邪魔にならないことに依存して、プログラムを書くことができます。

ただし、非volatileポインターを介してアクセスする場合、コンパイラーは、as-(as-)の下で、プログラムに目に見える副作用がないことを証明できるストアを自由に最適化できます。 ifルール。この単純な例では、これが行う可能性のある最悪の事態は、光が不規則に点滅するか、おそらくは永遠に暗くなることです。しかし、C標準はblinkenlightsと0xc0103について知りません。 volatile以外のポインターを介して変数にアクセスすることでどのような結果が許容されるかを言うことはできません。別の変数は、少なくとも1秒に1回インクリメントする必要がある「ハートビート」レジスタを参照する場合があり、そうしないと、安全メカニズムがヒューズを作動させてデバイスを爆発させる可能性があります。したがって、「何かまたは何も起こらない可能性がある」という標準的な言い方は、「動作は未定義です」と言うことです。

0
5gon12eder

非常に簡単に言えば、揮発性変数への不揮発性アクセスは、定義済みまたは未定義のいずれかである必要があります。したがって、未定義のセクションを削除する場合は、定義する必要があります。私はあなたがそれをどのように行うのか興味があります。たとえば、次の動作を定義するだけです。

int* p = "address of variable which may or may not be volatile";
*p = 1;

* pが揮発性でない場合に効率的であり、* pが揮発性の場合に定義されます。そして、ほとんどポインタの使用はこのカテゴリに分類されることを忘れないでください。

0
gnasher729