26

マイクロコントローラは、特定のステータス条件をクリアするためにレジスタを読み取る必要があることがよくあります。データが使用されていない場合に読み取りが最適化されないようにするためのポータブルな方法はCにありますか?メモリマップドレジスタへのポインタが揮発性として宣言されていれば十分ですか?言い換えれば、以下は常に標準準拠のコンパイラで機能しますか?

void func(void)
{
   volatile unsigned int *REGISTER = (volatile unsigned int *) 0x12345678;

   *REGISTER;
}

このような機能を扱うと、コンパイラに依存する問題が発生することを理解しています。したがって、この場合、私のポータブルの定義は少し緩いです。私はそれが最も人気のあるツールチェーンで可能な限り広く機能することを意味します。

4

5 に答える 5

10

人々は、正確に何volatileを意味するのかについて非常に熱心に議論しています。ほとんどの人は、あなたが示す構成があなたが望むことをすることを意図していたことに同意すると思いますが、C標準の言語がC99の時点で実際にそれを保証するという一般的な合意はありません。(C2011では状況が改善された可能性があります。まだ読んでいません。)

非標準ですが、組み込みコンパイラによってかなり広くサポートされており、動作する可能性が高い代替手段は次のとおりです。

void func(void)
{
  asm volatile ("" : : "r" (*(unsigned int *)0x12345678));
}

(ここでの「volatile」は「asm」に適用され、「これは出力オペランドがなくても削除されない可能性があることを意味します。ポインターにも置く必要はありません。)

この構成の主な残りの欠点は、コンパイラが1命令のメモリ読み取りを生成するという保証がまだないことです。C2011では、を使用するだけで十分_Atomic unsigned int かもしれませんが、その機能がない場合は、保証が必要な場合は、実際の(空でない)アセンブリインサートを自分で作成する必要があります。

編集:今朝私に別のしわが発生しました。メモリ位置からの読み取りに、そのメモリ位置の値を変更するという副作用がある場合は、次のようにする必要があります。

void func(void)
{
  unsigned int *ptr = (unsigned int *)0x12345678;
  asm volatile ("" : "=m" (*ptr) : "r" (*ptr));
}

その場所からの他の読み取りの誤最適化を防ぐため。func(100%明確にするために、この変更はそれ自体のために生成されたアセンブリ言語を変更しませんが、特にfuncインラインの場合、周囲のコードの最適化に影響を与える可能性があります。)

于 2012-12-11T16:21:01.820 に答える
5

はい、C標準は、揮発性変数にアクセスするコードが最適化されないことを保証します。

C11 5.1.2.3 / 2

「揮発性オブジェクトへのアクセス、「...」はすべて副作用です」

C11 5.1.2.3 / 4

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

C11 5.1.2.3 / 6

「準拠する実装の最小要件は次のとおりです。

—揮発性オブジェクトへのアクセスは、抽象マシンのルールに従って厳密に評価されます。」

于 2012-12-12T15:11:48.320 に答える
2

IIRC、C標準は使用の定義が少し緩いので、*REGISTER必ずしも読み取りを行うと解釈されるわけではありません。

しかし、次のことを行う必要があります。

int x = *REGISTER;

つまり、メモリ参照の結果をどこかで使用する必要があります。ただし、は揮発性である必要はxありません。

更新:_unused変数の警告を回避するには、no-op関数を使用できます。静的および/またはインライン関数は、実行時のペナルティなしに最適化する必要があります。

static /*inline*/ void no_op(int x)
{ }

no_op(*REGISTER);

更新2:私はちょうどより良い関数を思いついた:

static unsigned int read(volatile unsigned int *addr)
{
    return *addr;
}

read(REGISTER);

現在、この関数は、読み取りと使用、および読み取りと破棄の両方に使用できます。8-)

于 2012-12-11T16:01:51.173 に答える
1

おそらくGNUC固有の拡張機能はあまり移植性がないと考えられていますが、ここに別の選択肢があります。

#define read1(x)  \
({ \
  __typeof(x) * _addr = (volatile __typeof(x) *) &(x); \
  *_addr; \
})

これは、次のアセンブラ行に変換​​されます(gcc x86でコンパイルされ、-O2で最適化されます): movl SOME_REGISTER(%rip), %eax?

私は同じアセンブラを以下から入手します:

inline read2(volatile uint32_t *addr) 
{ 
   return *addr; 
}`

...別の回答で提案されているように、ただし、read1()異なるレジスタサイズを処理します。8ビットまたは16ビットレジスタでの使用が問題になるかどうかはわかりませんread2()が、少なくともパラメータタイプに関する警告はありません。

于 2014-11-05T20:47:49.277 に答える
0

コンパイラは通常、アセンブリインラインを最適化しません(適切に分析するのは困難です)。さらに、それは適切な解決策のようです。レジスタをより明示的に制御する必要があり、アセンブリにとっては自然なことです。

マイクロコントローラーをプログラミングしているので、コードにはすでにアセンブリーが含まれていると思います。したがって、インラインアセンブリーを少し使用しても問題はありません。

于 2012-12-11T16:22:17.877 に答える