「++ i + ++ i」が悪いことは誰もが知っていますが、画面の背後には何がありますか?

あなたの画面の後ろから覗くバグo_o 間違いなく、すべてのプログラマーは、投稿のタイトルで与えられているような表現の使用が望ましくないだけでなく、厳密に禁忌であることを知っています。 このような構造、つまり定義されていないコンパイラの動作は、多くの微妙なエラーと望ましくない結果をもたらす可能性があります。 しかし、多くの初心者プログラマーは、この問題をより深く理解し、コンパイラーの画面の後ろを見て、そのような場合に正確に何が起こるかを知りたいと確信しています。 この投稿は、このようなコードの一例の研究に捧げます。 猫へようこそ:)



研究の対象



例として、2つの異なる言語(CおよびC#)で式「 ++i + ++i



」の操作を分析します。 ご存じのとおり、1つ目はネイティブプロセッサコードにコンパイルされ、2つ目は大まかに言えば仮想スタックマシンに基づいて動作します。 したがって、例自体を検討してください。



Cのソース:
  1. #include <stdio.h>
  2. void main()
  3. {
  4. int i = 5;
  5. i = ++ i + ++ i;
  6. printf( "%d \ n" 、i);
  7. }


C#のソース:
  1. システムを使用して ;
  2. パブリック クラステスト
  3. {
  4. public static void Main()
  5. {
  6. int i = 5;
  7. i = ++ i + ++ i;
  8. Console .WriteLine(i);
  9. }
  10. }


Cスクリーンの後ろ



逆アセンブラを使用して、Cコンパイラが最終的に生成したものを見てみましょう。



 #A#5:int i = 5;                        
   cs:0295 BE0500 mov si、0005  
 #A#6:i = ++ i + ++ i;                    
   cs:0298 46 inc si       
   cs:0299 46 inc si       
   cs:029A 8BC6 mov ax、si    
   cs:029C 03C6 add ax、si    
   cs:029E 8BF0 mov si、ax    
 #A#7:printf( "%d \ n"、i);                 
   cs:02A0 56プッシュsi       
   cs:02A1 B8AA00 mov ax、00AA  
   cs:02A4 50プッシュa       
   cs:02A5 E8330C call _printf  


リストからわかるように、コンパイラーは変数i



x86



SI



レジスターにマップしました。 その後、このレジスターを2回インクリメントし、 AX



バッテリーを介して自分自身に追加しました。 その結果、変数i



は14に等しくなります。



C#画面の背後



Ildasmを使用して C#の背後にあるものを見てみましょう。



 .method public hidebysig static void Main()cil managed
 {
   .entrypoint
   //コードサイズ21(0x15)
   .maxstack 3
   .locals init(int32 V_0)
   IL_0000:ldc.i4.5 // 5 5をプッシュ
   IL_0001:stloc.0 // i:= pop()null
   IL_0002:ldloc。0 // iiをプッシュ
   IL_0003:ldc.i4.1 // 1 i、1をプッシュ
   IL_0004:add // push(pop()+ pop())(i + 1)つまり  6
   IL_0005:dup //スタックの先頭をコピー6、6
   IL_0006:stloc。0 // i:= pop()// i:= 6 6
   IL_0007:ldloc。0 // i 6、iをプッシュ
   IL_0008:ldc.i4.1 // 1 6、i、1をプッシュ
   IL_0009:add // push(pop()+ pop())6、(i + 1)つまり  7
   IL_000a:dup //スタックの先頭をコピー6、7、7
   IL_000b:stloc。0 // i:= pop()// i:= 7 6、7
   IL_000c:add // push(pop()+ pop())13
   IL_000d:stloc.0 // i:= pop()null
   IL_000e:ldloc。0 // iiをプッシュ
   IL_000f:void [mscorlib] System.Consoleを呼び出す:: WriteLine(int32)
   IL_0014:ret
 } //メソッドテストの終わり::メイン


明確にするために、アセンブラーの仮想スタックマシンにコメントを追加しました。 リストを見ると、変数i



をインクリメントするために、変数自体と変数がスタックにプッシュされていることがわかります。 次に、スタックから2つの値を取得して追加する追加コマンドが実行され、結果がスタックに戻されます。 次に、スタックの先頭を複製し、変数に値を書き戻します。 したがって、 5 + 1



スタックに残ります。 6.次に、別の増分でサイクルが繰り返されます。変数がスタックにプッシュされ、続いてユニット、追加、頂点の複製が発生し、2番目の増分の結果が変数に書き戻されます。 i



は7があり、最初のケースから6つ、2番目から7つがスタックに残ります。 次に、追加コマンドが実行され、結果が13になり、変数に入力されます。



まとめ



ここにそのような同一のものがあります、コードは異なる条件で完全に異なって実行されます。 プログラムではこの種のコードを避けてください。



トピックが興味深いと思われる場合-お知らせください。編集の世界から興味深い瞬間をいくつか書きます。



UPD コメントは、 PHPJavaActionscript 3JavaScriptおよびTCLでは結果も13ですが、 PerlK ++およびCではGCCは14であることを示唆しています (ただし、GCCの結果はオプティマイザ設定に依存する場合があります):)



PL / SQLPython -10は区別されています(K.O.が示唆しているように-言語に増分がないため) -Bash -12。



また、このようなコードの作成を許可しないコンパイラもあります。 たとえば、 Ruby (またはそのような)。



そして、あなたの%username%コンパイラの結果はどうなりますか?



UPD2 。 関連リンク: ウィキペディアのフォローポイント、 Alyona C ++



All Articles