C++(の判断フロー)をアセンブリの目で見る


【声明:著作権所有、転載歓迎、商業用途に使用しないでください.連絡ポスト:[email protected]
私たちの普段のプログラミングでは、判断に用いるところが多いが、主にif-elseという3つの方法がある.switch;?:.最後の方法は本質的にif−elseと同じである.switchとif-elseも実は同じで、もし私たちがswitchをifに変えたら(...){} else if(...) {}else{}では、あなたが実現した効果とswitchは実際にはあまり悪くなく、よく知っている友达はこのような体験をします.信じられない方もいるかもしれませんが、自分で実例を書いて比較してみてください.
(1)switchのbreakは重要ですか?
21:       int m = 10;
004017A8   mov         dword ptr [ebp-4],0Ah
22:       switch(m)
23:       {
004017AF   mov         eax,dword ptr [ebp-4]
004017B2   mov         dword ptr [ebp-8],eax
004017B5   cmp         dword ptr [ebp-8],0Ah
004017B9   je          process+33h (004017c3)
004017BB   cmp         dword ptr [ebp-8],0Bh
004017BF   je          process+42h (004017d2)
004017C1   jmp         process+4Fh (004017df)
24:       case 10:
25:           printf("ten!
"); 004017C3 push offset string "ten!
" (0046f028) 004017C8 call printf (004214d0) 004017CD add esp,4 26: break; 004017D0 jmp process+4Fh (004017df) 27: 28: case 11: 29: printf("eleven!
"); 004017D2 push offset string "eleven!
" (0046f01c) 004017D7 call printf (004214d0) 004017DC add esp,4 30: break; 31: 32: default: 33: break; 34: } 35: return; 36: }
の上のアセンブリコードはbreakがあるとき、関数がどのようにコンパイルされているかを説明しています.アドレス0 x 4017 AFから,CPUはmの判断に集中し始める.まず、mのデータはeaxに割り当てられ、その後、eaxはスタック【ebp-8】のメモリにコピーされる.次に、比較データ10、すなわち16進0 Aを開始し、両者が等しい場合、コードは0 x 4017 C 3処理にジャンプする.しかし、等しくなければ、命令は元の順序で下に進み続け、11と比較し続け、比較に成功すればアドレス0 x 4017 d 2で実行され、等しくなければswitchモジュールから飛び出し、アドレス0 x 4017 dfで実行されるしかない.前述したように、データが10または11と比較的成功すれば、対応するcase文にジャンプして実行を続けますが、比較が終わった後、元の位置にジャンプしますか?アセンブリコードは、彼らができないことを教えてくれます.case比較が終了したら、アドレスox 4017 dfにも報告するからです.
もしここcase 10の後ろにbreakがなかったら?状況は違うのではないでしょうか.
004017A8   mov         dword ptr [ebp-4],0Ah
22:       switch(m)
23:       {
004017AF   mov         eax,dword ptr [ebp-4]
004017B2   mov         dword ptr [ebp-8],eax
004017B5   cmp         dword ptr [ebp-8],0Ah
004017B9   je          process+33h (004017c3)
004017BB   cmp         dword ptr [ebp-8],0Bh
004017BF   je          process+40h (004017d0)
004017C1   jmp         process+4Dh (004017dd)
24:       case 10:
25:           printf("ten!
"); 004017C3 push offset string "ten!
" (0046f028) 004017C8 call printf (004214d0) 004017CD add esp,4 26: 27: case 11: 28: printf("eleven!
"); 004017D0 push offset string "eleven!
" (0046f01c) 004017D5 call printf (004214d0) 004017DA add esp,4 29: break; 30: 31: default: 32: break; 33: } 34: return; 35: }
case 10の後でbreak文をキャンセルしました.従来の状況とは少し異なり、case 10はprintf関数を実行した後、現在のswitchモジュールから飛び出しず、case 11の文を実行し続けることが分かった.結果を出力するとtenもelevenも印刷されていることがわかります.このような明確な流れを見て、breakの重要性についてまた新しい認識があると信じています.アセンブリの前では、すべてが一目瞭然です.
(2)ifの中の&&和はどのように演算されますか?
同様に、次の例を見ることができます.
21:       int m = 10;
004017A8   mov         dword ptr [ebp-4],0Ah
22:       int n = 0;
004017AF   mov         dword ptr [ebp-8],0
23:
24:       if(m == 10 && n == 0)
004017B6   cmp         dword ptr [ebp-4],0Ah
004017BA   jne         process+3Fh (004017cf)
004017BC   cmp         dword ptr [ebp-8],0
004017C0   jne         process+3Fh (004017cf)
25:       {
26:           printf("&&!
"); 004017C2 push offset string "&&!
" (0046f020) 004017C7 call printf (004214e0) 004017CC add esp,4 27: } 28: 29: if(m == 10 || n == 0) 004017CF cmp dword ptr [ebp-4],0Ah 004017D3 je process+4Bh (004017db) 004017D5 cmp dword ptr [ebp-8],0 004017D9 jne process+58h (004017e8) 30: { 31: printf("||"); 004017DB push offset string "||" (0046f01c) 004017E0 call printf (004214e0) 004017E5 add esp,4 32: }
&&はC言語では与の意味であり、ということは、両者とも真である.あるいは両方のどちらかが真であればよいという意味です.これは対応するアセンブリの文にもよく表れている.まず私たちは状況を見ます.アドレス0 x 4017 B 6の命令から,まずmデータを比較し,次にnデータを比較することが分かった.これはスタック内のオフセット値から見ることができる.mと10を比較すると、nと0を比較する機会があり、比較に失敗すると、アドレス0 x 4017 cfにジャンプして実行し、現在の判断モジュールから飛び出します.nと0の比較も同様で、両方が成功してこそ、アドレス0 x 4017 C 2に入って実行し、印刷&&&.対応するか、または、アドレス0 x 4017 cfで比較を開始したのもデータmであり、次いでデータnであることが分かった.とは異なり、mデータとnデータは一方が成功すれば、アドレス0 x 4017 dbにジャンプして実行される.両方が偽物である場合にのみ、現在のモジュールから飛び出します.従って、2つのjneは和の基礎を構成し、1つのjeと1つのjneはまたは基石を構成している.もし&&の選択肢と||の選択肢が重ねられたらどうなるか、自分で試してみてはいかがでしょうか.やってみる.
まとめ:
if-elseという二分判断構造は実際には現在のプログラミングにおいて特に重要であり,特に基礎でもある.二分法について最も顕著なコードは,順序データで二分検索を行うことである.もし興味があれば、自分でペンを動かしてみてもいいですか?ここでは、いくつかのアドバイスがあります.
(1)関数入力のインデックス範囲が整然としていることを確認する(start(2)中間点を計算するときに範囲オーバーフローに注意(middle=start+(end-start)>>1)
(3)あなたの関数を汎用的な二分法に変更して関数を探すことを考える(const void*と関数ポインタを使うことを考えることができる)
【予告:次のブログの内容は、C++のループ判断をまとめた目で見ることです】