私は DSP チップ用のコンパイラを使用しています。このコンパイラは、C コードから配列の終わりを超えてアクセスするコードを意図的に生成しますが、そうではありません!
これは、反復の終わりに次の反復のために一部のデータをプリフェッチするようにループが構造化されているためです。したがって、最後の繰り返しの最後にプリフェッチされたデータは、実際には使用されません。
そのような C コードを書くと、未定義の動作が発生しますが、それは移植性を最大化することに関する標準文書の形式にすぎません。
多くの場合、範囲外にアクセスするプログラムは巧妙に最適化されていません。それは単にバグです。コードはガベージ値をフェッチし、前述のコンパイラの最適化されたループとは異なり、コードはその後の計算で値を使用するため、値が破損します。
そのようなバグをキャッチすることは価値があるため、その理由だけでも動作を未定義にする価値があります。ランタイムが「main.c の 42 行目の配列オーバーラン」のような診断メッセージを生成できるようにするためです。
仮想メモリを備えたシステムでは、後に続くアドレスが仮想メモリのマップされていない領域にあるように配列が割り当てられることがあります。アクセスすると、プログラムが爆撃されます。
余談ですが、C では、配列の末尾を 1 つ過ぎたポインタを作成することが許可されていることに注意してください。そして、このポインターは、配列の内部へのポインターよりも大きくなければなりません。これは、C 実装がメモリの最後に配列を配置できないことを意味します。この場合、1 プラスのアドレスがラップアラウンドし、配列内の他のアドレスよりも小さく見えます。
それにもかかわらず、初期化されていない値や範囲外の値へのアクセスは、移植性が最大ではない場合でも有効な最適化手法である場合があります。これは、たとえば、Valgrind ツールが初期化されていないデータへのアクセスが発生したときに報告せず、プログラムの結果に影響を与える可能性のある何らかの方法で値が後で使用された場合にのみ報告する理由です。「xxx:nnn の条件付き分岐は初期化されていない値に依存します」のような診断が表示され、その発生源を突き止めるのが難しい場合があります。このようなすべてのアクセスがすぐにトラップされた場合、適切に手動で最適化されたコードだけでなく、コンパイラで最適化されたコードからも多くの誤検知が発生します。
そういえば、Linux に移植して Valgrind で実行すると、これらのエラーが発生するベンダーのコーデックを使用していました。しかし、ベンダーは、数ビットしかないと私に確信させました使用されている値の実際には初期化されていないメモリからのものであり、それらのビットはロジックによって慎重に回避されました。値の適切なビットのみが使用され、Valgrind には個々のビットまで追跡する機能がありません。初期化されていない素材は、エンコードされたデータのビット ストリームの末尾を超えて 1 ワードを読み取った結果ですが、コードはストリーム内のビット数を認識しており、実際のビット数よりも多くのビットを使用することはありません。ビット ストリーム配列の末尾を超えたアクセスは、DSP アーキテクチャに悪影響を与えないため (配列の後に仮想メモリがなく、メモリ マップ ポートがなく、アドレスがラップされない)、これは有効な最適化手法です。
ISO Cによると、C標準で定義されていないヘッダーを単にインクルードしたり、プログラム自体またはC標準で定義されていない関数を呼び出したりすることは、未定義の例であるため、「未定義の動作」は実際にはあまり意味がありません行動。未定義の動作は、「地球上の誰にも定義されていない」という意味ではなく、単に「ISO C 標準で定義されていない」という意味です。しかしもちろん、未定義の動作は実際には誰にも定義されていないことがあります。