私たちの周りの配列、ポインター、その他の量子現象

私たち全員がマトリックスに住んでいるとは言いたくありませんが、転がるボールの同じ音が疑わしく隣人をシミュレートするために使用されます。






この投稿はそのタイトルと完全に一致しています。 そもそも、Cケルニガンとリッチーの古典と同様に、標準の主張に反して、配列インデックスの使用は対応するポインターの使用とまったく同等ではなく、最後にエピグラフの選択が明確になることが示されます。 そして、はい-投稿の中央も空ではありません。



それはすべて、フェルマーの定理の反論から始まり、コンパイラの未定義の動作(未定義の動作またはUB)に関する強く考えられた投稿から、 1より大きいコサインを持つ数を見つけることから始まりました 。 つまり、そのような投稿がいくつかありましたが、 このhabrahabrへの翻訳 )では、次の単純なコードと、その作業の明らかな結果からはほど遠いものが与えられました:



int table[4]; bool exists_in_table(int v) { for (int i = 0; i <= 4; i++) { if (table[i] == v) return true; } return false; }
      
      





ループ条件にエラーがあります:「<4」ではなく「<= 4」。

テーブルをゼロで埋め、この配列の不正な5番目の要素が0であることを確認してから、テーブルの42番、つまり exists_in_table(42)を呼び出すと、そこに確実に見つかります。最適化されたCコンパイラの場合、この関数は言語標準に完全に準拠してtrueを返します。



説明は簡単です: exists_in_table()は、最初の4回の繰り返しのいずれかでtrueを返すか、 テーブル[4] (UB)を読み取る必要があります。この場合、 exists_in_table()はC言語標準に従って何でもできます- 、およびtrueを返します 。 つまり、コンパイラはすべてのexists_in_table()コードを最適化してtrue返すことができます。



そして、これは抽象的な理論ではなく、具体的な実践です。 これがコードの最適化方法です。 exists_in_table(42)は、コンパイル時に、たとえば-O2または-O3オプションを使用してgcc 6.3(MinGW経由)で trueを返します。 以下の結果はすべて、この特定の構成に関連しています。



今、 私たちの生活の中で少なくとも一度は標準Cに報酬を与えましょう。

はい、配列のセクションでは、配列の境界外の要素へのアクセスはUBであると正直に警告しています。 しかし、Cを研究したことがある人なら誰でも知っているように、配列要素へのアクセスはポインターを介して可能です。または、ISO / IEC 9899標準の言語では、「インデックス演算子[]の定義は次のとおりです。E1[E2]は(*((E1)+ (E2)))。」(「添え字演算子[]の定義は、E1 [E2]が(*((E1)+(E2))))と同一であることです。



そして、興味深いことが明らかになります。 「同一」-つまり、どのような状況でも同じように交換可能です。 そしてUBでも? 上記の例で、配列要素にアクセスするためにポインターが使用された場合に何が起こるかを調べてみましょう。

しかし、最初に、コードを本当に安全にしましょう。テーブル配列を構造でラップして、その隣に割り当てられるようにします。配列の境界を越えても、だれも気にせず、たとえば、隣接する配列の内容を同時にチェックします。



 struct taddummy{ int table[4]; int dummy[4]; } tadam; int exists_in_table(int v) { for (int i = 0; i < 8; i++) { if (tadam.table[i] == v) return true; } return false; } int main() { for (int i = 0; i < 8; i++) { tadam.table[i] =0; } printf("%d\n",exists_in_table(42)); }
      
      





このコードもtrueを返すことを信じるか検証できます-UBの観点からは、何も変更されていません。



そして今、標準に従って、 tadam.table [i]*(tadam.table + i)に置き換えます。



 int exists_in_table(int v) { for (int i = 0; i < 8; i++) { if (*(tadam.table+i) == v) return true; } return false; } int main() { for (int i = 0; i < 8; i++) { *(tadam.table+i)=0; } printf("%d\n",exists_in_table(42)); }
      
      





プログラムを実行し、 exists_in_tableが0を返すようにします。つまり、配列内の数値42は実際にはそうではありません。 現実には、すべてが実際の状態ではありません。



そして、これは簡単に説明されています-C標準によれば、ポインタの使用と逆参照には独自のUBケースもあります-割り当てられたメモリへのアクセス、nullポインタの逆参照など... ...それらの間に「配列の境界を越える」ケースはありません-ポインタ配列については何も知らず、期待どおりに動作します。



つまり、一般的な誤解とは反対に、配列インデックスとコンパイラーの対応するポインターはまったく同じではなく、粒子の特性(配列の境界内)を示し、時には波(UB領域)を示す電子のようなものです)



さらに、このアナロジーをさらに発展させることができます。配列内に42が存在することに驚いた場合、 exists_in_tableに値42の特定の要素番号を指定して、コードを



 if (table[i] == v) return i;
      
      





または、この行を変更せずに、対応するtable [i]要素の値を画面の前に挿入するだけで、配列が境界を離れると、UB効果は完全に消えます。関数はfalseを返します。彼の存在下では波はなく、ハードコアのみ、パーティクルのみ。



これで、より高いエネルギーレベルに移動した場合-配列やポインター、さらにはUBからも気をそらし、コンパイラー全体の動作を見ると、この効果は続きます。



たとえば、インテルでの私の仕事の一部は、サードパーティコードの重要なコンポーネントのパフォーマンスを最適化することです。 このコードは商業的な秘密であるため、通常はプロジェクトの完了後に彼らは私を殺しますが、私は彼らから数十行の機能しか得られません 。 そして、通常、関数自体は、プロファイラーが十分な精度でそれを表示できないほど重要でない期間にわたって動作し、何百万回の呼び出しのために単にコードのボトルネックであるため、最適化結果を評価するために使用する必要がありますのようなもの



  i1 = __rdtsc(); for (i = 0; i < 100000;i++) { performance_test(); } i2 = __rdtsc();
      
      





しかし、この特定のコードではなく、「タイプの何か」です。 上記のコードフラグメントに最適化コンパイラを使用すると、サイクル全体が単一の反復に単純に短縮され、他のすべてが破棄され、実行時間が予想からはほど遠いものになります。 サイクル全体を正直に実行するには、何かを出力するか、単にvolatile変数にアクセスする必要があります。これは、外部世界との相互作用の一種でもあります。



 volatile res = performance_test();//  .
      
      





つまり、最適化コンパイラーの人には、奇妙で反直感的な法則(しかし法則!)に従って動作するシステムがあります。外部の顕現だけが重要であり、内部状態は任意、不明確、そして最も重要であると見なすことができます。内部状態では、魔法は消え、システムは教科書のように退屈で予測可能な振る舞いを始めます。







しかし、これは「完全に」という言葉からの量子力学です。シュレーディンガー猫(42番)から、配列の境界を越えて探しています。内部を見るまで、何かが絡まっていると仮定できます。量子テレポーテーション(たとえば、 呼び出し不可能な関数の呼び出し )。 そして、最後に、量子力学的な「オブザーバー」の出現、つまり、コードが何か(外部)と相互作用し、その動作がオプティマイザーに認識されず、デコヒーレンスが発生し、システムが古典的なものに崩壊する状況-この場所の深い最適化、したがって、魔法は「不可能になります。



私が出会ったときに私が話し始めたのは、この前の段落のカップルでした dibr 。 ディマと私は荒れ狂う若者によってつながっているので、すぐに彼を中断しようとしました。 しかし、ディマを止めるのはそれほど簡単ではなく、時にはとてもクールです。



だから、続けた dibr 、-「これは、私たちの世界の量子力学が私たちの世界のためにのみ可能であることが判明したことを意味します」.... そして、私は彼とコーラスで繰り返したフレーズの終わりに来ました-「深い最適化で組み立てられました。 つまり、私たちはデバッグ版ではなく最終リリースに住んでいます!」



もちろん、これは良いことです-デバッグ版の動作は完全に予測可能ですが、その中には、通常、作業の過程で、さらに多くのバグがあります。 しかし、一方で、これは不可解です。まず、鉄資源が埋蔵量なしで十分に「近い」ところで最適化が必要であり、文明の発展の可能性を考えると、私たちの未来は非常にあいまいです。 そして第二に、宇宙の発展に関する文書によれば、古典力学のみが利用可能であると想定されていたことがわかりました。つまり、温度計に過ぎず、電子工学全般、特にコンピューターの現代の急速な発展は、内部の「機能深宇宙の最適化に関連するユニバース」は、システムを理解し、実際にそれらを活用するエクスプロイトを作成することができました。



この概念では、なぜ量子力学がそれほど非自明で「非人道的」であるかが明らかになります。これは、私たちの目ではない宇宙内部の「ハック」であり、世界への標準インターフェースはすべてが単純で、論理的です。



そして、結論として、ドミトリーを再び引用します。「そして今、宇宙開発者はおそらく、起こっていることのst迷で、ターミナルに座っています(彼はこの爆発的な発展ではなく、古典的な物理学でゆっくりと発展する文明を期待し、「宇宙を破壊する」)しかし、プロセスは中断しません。まず、多くの計算リソースが投入され、それを捨てるのは残念です。次に、開発ブランチは興味深いです。あなたはそれを観察することができます、あなたはユニバーソロジーに関する記事の非自明な結果を見て、再び、次の宇宙をコンパイルするとき何がはっきりするでしょう 避けるために。」



All Articles