web-dev-qa-db-ja.com

揮発性配列を非揮発性配列にキャスト

データが割り込みで書き込まれるグローバルな揮発性の符号なし文字配列_volatile unsigned char buffer[10]_があります。 unsigned char *を取り、その値をハードウェア(EEPROM)void storeArray(unsigned char *array)に保存する関数があります。この例では、最初の3つの値です。このように揮発性配列を不揮発性配列にキャストしても安全ですか?

_store_array((unsigned char *) buffer);
_

私は以下を読みましたが、よくわかりませんが、気になります。

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

これは私のコードに影響しますか?

次に、このフォローアップの質問があります。バッファ配列には、保存したいデータのセクションしかありません(変更できません)。この例では、3番目の値で始まります。以下を行うことは合法ですか?

_store_array((unsigned char *) buffer + 3);
_

もしそうなら、_3_が配列に追加された場合、キャストはどのように影響を受けますか? BRそしてありがとう!

編集:@Cacahuete Fritoは非常に類似した質問をリンクしました: `memcpy((void *)dest、src、n)`は `volatile`配列で安全ですか?

14
earthling

はい、あなたが投稿した標準的な引用はあなたがやろうとしていることを正確にカバーしています。キャストを実行することで、配列内のオブジェクトが実際にはunsigned charであるときにvolatile unsigned charであると見なしているため、関数内ではvolatileオブジェクトをvolatile修飾子のないlvalue。未定義の動作。

関数storeArrayを変更できない場合は、データを関数に渡す前に、データを揮発性配列から不揮発性配列にコピーする必要があります。

2番目の質問について:ポインター演算は問題ありません。bufferunsigned char*に変換し、結果のポインターに3を追加して、buffer[3]を指します(ただし、修飾は間違っています) )。

標準の正しいセクションを見つけました。このコードは未定義の動作につながります。

「ハードウェア」に何かを書き込む関数には、「ハードウェア」が何であるかに応じて、おそらくvolatile- qualifierパラメーターが必要です。メモリマップレジスタ、DMAバッファ、または不揮発性メモリの場合、パラメータは必ずvolatile unsigned char*(またはオプションでvolatile uint8_t*文字タイプと見なされます)。


詳細:Cでは、文字ポインターを使用してデータの任意のチャンクを反復処理できます。C176.3.2.3/7:

オブジェクトへのポインターが文字型へのポインターに変換されると、結果はオブジェクトのアドレス指定された最下位バイトを指します。オブジェクトのサイズまで、結果を連続的にインクリメントすると、オブジェクトの残りのバイトへのポインターが生成されます。

「左辺値」へのアクセスについて引用する部分は、その場所に実際に格納されているものとは異なるポインター型を介してデータにアクセスすることを指します。明らかに:それを指すさまざまなポインタをどれだけキャストしても、実際のデータは元の型を保持します。

間違ったポインタ型を介してデータにアクセスすることは通常は許可されていませんが、ここでも文字アクセスは「厳密なエイリアス規則」C17 6.5/7の特別な例外です。

オブジェクトは、次のいずれかのタイプの左辺値式によってのみアクセスされる格納された値を持つ必要があります。
...
-文字タイプ。

したがって、文字ポインターを介してあらゆる種類のデータにアクセスできますが、そのポインターが揮発性修飾されていない場合、引用した部分C17 6.7.3/5に従って未定義の動作を呼び出します。

実際には、不揮発性ポインター型を使用すると、コンパイラーが予期しない方法でアクセスを最適化する可能性があります。したがって、これは理論上の「言語の法則」だけではなく、実際には最適化を有効にして非常に奇妙なコードを生成する可能性があります。組み込みシステムのバグを見つけるのが非常に難しいのは、このようなvolatileがないためです。


あなたのフォローアップの質問に関して、キャストとbuffer + 3は何も変更しません:volatile修飾子なしの文字ポインターを処理しています-同じ型。実際のデータはvolatile unsigned charタイプのままなので、unsigned char*を介して関数からアクセスすることはできません。

6
Lundin
  1. 配列が割り込みの変更である場合、アクセスしてアトミックな方法で変更するメカニズムを提供する必要があります。これを行わないと、RWまたはRMW操作が失敗し、データが不整合になる可能性があります。

  2. 揮発性データにアクセスすると、f = unctionパラメータも揮発性になります。 storeArray(volatile unsigned char *)およびキャストは必要ありません。キャストは警告のみを削除します。不揮発性データを渡しても、同様に機能します。

3
P__J__

あなたが見つけたように、あなたは「未定義の振る舞い」に依存しています。ただし、コンパイル単位の分離(および「プログラム全体の最適化」(WPO)など)によっては、おそらく機能します。ほとんどの場合、コンパイラー(少なくともgcc)は、異なるコンパイル単位の関数間で配列アクセスを最適化するのに「十分にスマート」ではありません。とは言っても、クリーンで安全で移植可能な方法は、配列をコピーして、不揮発性配列の値の揮発性の値への依存性をコンパイラーに認識させることです。

1
JimmyB