7

x86ベースのコアを使用して32ビットメモリマップドレジスタを操作しています。私のハードウェアは、CPUが32ビット幅の読み取りと書き込みをこのレジスタに生成する場合にのみ正しく動作します。レジスタは32ビットアドレスにアラインされており、バイト粒度でアドレス指定できません。

C(またはC99)コンパイラがすべての場合に完全な32ビット幅の読み取りと書き込みのみを生成することを保証するにはどうすればよいですか?

たとえば、次のような読み取り-変更-書き込み操作を実行すると、次のようになります。

volatile uint32_t* p_reg = 0xCAFE0000;
*p_reg |= 0x01;

一番下のバイトだけが変更され、8ビット幅の読み取り/書き込みが生成されるという事実についてコンパイラーが賢くなりたくありません。x86での8ビット演算では、マシンコードの密度が高くなることが多いため、不要な最適化が心配です。一般に、最適化を無効にすることはできません。

-----編集-------
興味深く、非常に関連性の高い論文:http ://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

4

5 に答える 5

6

あなたの懸念はvolatile予選によってカバーされます。

6.7.3/6「型修飾子」は次のように述べています。

揮発性修飾型のオブジェクトは、実装に未知の方法で変更されたり、その他の未知の副作用が発生したりする可能性があります。したがって、そのようなオブジェクトを参照する式は、5.1.2.3で説明されているように、抽象マシンのルールに従って厳密に評価されるものとします。さらに、すべてのシーケンスポイントで、オブジェクトに最後に格納された値は、前述の未知の要因によって変更された場合を除き、抽象マシンによって規定された値と一致する必要があります。揮発性修飾型を持つオブジェクトへのアクセスを構成するものは、実装定義です。

5.1.2.3「プログラムの実行」は(とりわけ)次のように述べています。

抽象マシンでは、すべての式がセマンティクスで指定されたとおりに評価されます。

この後に、一般に「as-if」ルールと呼ばれる文が続きます。これにより、最終結果が同じである場合、実装は抽象マシンのセマンティクスに従わなくなります。

実際の実装では、値が使用されておらず、必要な副作用(関数の呼び出しや揮発性オブジェクトへのアクセスによるものを含む)が発生していないと推測できる場合は、式の一部を評価する必要はありません。

ただし、6.7.3 / 6は基本的に、式で使用される揮発性修飾型に「as-if」ルールを適用できないことを示しています。実際の抽象マシンのセマンティクスに従う必要があります。したがって、揮発性32ビット型へのポインタが逆参照される場合は、32ビット値全体を読み取るか書き込む必要があります(操作によって異なります)。

于 2010-06-14T22:45:55.677 に答える
4

コンパイラが正しいことを行うことを保証する唯一の方法は、ロードおよびストアルーチンをアセンブラに記述し、Cから呼び出すことです。私が長年使用してきたコンパイラの100%は、間違っている可能性があります(GCCを含む) 。

オプティマイザによって、たとえば、コンパイラに0x10の小さな数値として表示される定数を、32ビットレジスタに格納したい場合があります。これは、具体的に質問した内容であり、それ以外の場合は優れたコンパイラが実行しようとしていることです。 。一部のコンパイラは、32ビット書き込みではなく8ビット書き込みを実行して命令を変更する方が安価であると判断します。コンパイラがバスを想定しているメモリサイクルだけでなく、プログラムスペースを節約しようとしているため、可変命令長ターゲットはこれをさらに悪化させます。(たとえば、mov eax、0の代わりにxor ax、ax)

そして、gccのように絶えず進化しているものでは、今日動作するコードは明日動作する保証はありません(gccの一部のバージョンを現在のバージョンのgccでコンパイルすることさえできません)。同様に、デスクのコンパイラで機能するコードは、他の人にとっては普遍的に機能しない場合があります。

推測と実験を取り除いて、ロード関数とストア関数を作成します。

これの副次的な利点は、コードを何らかの方法でシミュレートしたり、コードを金属ではなくアプリケーションスペースで実行したりする場合、またはその逆の場合に、優れた抽象化レイヤーを作成できることです。その逆の場合は、アセンブラー関数を置き換えることができます。シミュレートされたターゲットを使用するか、デバイスを搭載したターゲットにネットワークを横断するコードに置き換えます。

于 2010-06-14T22:35:33.387 に答える
0

ハードウェアにアクセスするときにバイト(unsigned char)型を使用しない場合、コンパイラが8ビットのデータ転送命令を生成しない可能性が高くなります。

volatile uint32_t* p_reg = 0xCAFE0000;
const uint32_t value = 0x01;  // This trick tells the compiler the constant is 32 bits.
*p_reg |= value;

ポートを32ビット値として読み取り、値を変更してから、書き戻す必要があります。

uint32_t reg_value = *p_reg;
reg_value |= 0x01;
*p_reg = reg_value;
于 2010-06-14T20:17:13.270 に答える
0

さて、一般的に言って、レジスタが32ビットの揮発性として入力されている場合、上位バイトが最適化されるとは思いません。volatileキーワードを使用しているため、コンパイラーは上位バイトの値が0x00であると想定できません。したがって、8ビットのリテラル値のみを使用している場合でも、完全な32ビットを書き込む必要があります。0x86またはTiプロセッサ、またはその他の組み込みプロセッサでこれに関する問題が発生したことはありません。通常、volatileキーワードで十分です。少し奇妙になるのは、プロセッサが書き込もうとしているワードサイズをネイティブにサポートしていない場合だけですが、32ビット数の0x86では問題にはなりません。

コンパイラが4ビット書き込みを使用する命令ストリームを生成することは可能ですが、それは単一の32ビット書き込みに対するプロセッサ時間または命令スペースの最適化ではありません。

于 2010-06-14T19:11:27.457 に答える
0

ハードウェアに対するリードモディファイライト操作は常に複数の命令で行う大きなリスクであるため、ほとんどのプロセッサは、中断できない単一の命令でレジスタ/メモリを操作する命令を提供します。

操作しているレジスタのタイプによっては、変更フェーズ中に変更され、誤った値が書き戻される可能性があります。

これが重要な場合は、dwelchが提案するように、アセンブリで独自の読み取り-変更-書き込み関数を作成することをお勧めします。

型を最適化する(最適化を目的として型変換を行う)コンパイラについては聞いたことがありません。int32として宣言されている場合、それは常にint32であり、常にメモリ内で正しく整列されます。コンパイラのドキュメントをチェックして、さまざまな最適化がどのように機能するかを確認してください。

私はあなたの懸念がどこから来ているのか、構造を知っていると思います。構造は通常、最適な位置にパディングされます。これが、バイトアラインメントを取得するために#pragma pack()をラップする必要がある理由です。

アセンブリを1ステップ実行するだけで、コンパイラがコードをどのように変換したかがわかります。私はそれがあなたのタイプを変えていないとかなり確信しています。

于 2010-06-14T23:11:49.287 に答える