寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。本人非計算機專業,可能對本教程涉及的事物沒有了解的足夠深入,如有錯誤,歡迎批評指正。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 (一)羽夏看C語言——簡述 ,方便學習本教程。
if語句
生活中,經常會有選擇或者情況需要自己判斷,計算機也是如此。所有的判斷語句還是后面將要介紹的循環其實都是由JCC指令
組成的。我們先給出如下代碼示例:
#include <iostream>
using namespace std;
//如果是C,請自行將頭文件包含改為 stdio.h 和 stdlib.h
//將 cout 改為 printf_s(可以 printf,但微軟編譯器編譯會報錯,自行科普)
int main()
{
int c = 0;
int re = 0;
cout << "請輸入數字:" << endl;
cin >> c;
if (c==0)
{
re = -c;
}
else if (c==1)
{
re = c + c;
}
else if (c==2)
{
re = 4;
}
else if (c==3)
{
re = c * c;
}
else if (c==4)
{
re = c + c + 5;
}
else if (c==5)
{
re = c;
}
else
{
re = -1;
}
system("pause");
return 0;
}
然后查看一下它的反匯編:
switch語句
switch
語句在多情況判斷上是用的最多的,是if
語句的升級版,絕大多數情況比單純的if-else
高效的多,下面我們用代碼揭開它神秘的面紗:
#include <iostream>
using namespace std;
//如果是C,請自行將頭文件包含改為 stdio.h 和 stdlib.h
//將 cout 改為 printf_s(可以 printf,但微軟編譯器編譯會報錯,自行科普)
//將 cin 改為 scanf_s(可以 scanf,但微軟編譯器編譯會報錯,自行科普)
int main()
{
int c = 0;
int re = 0;
cout << "請輸入數字:" << endl;
cin >> c;
switch (c)
{
case 0:
re = -c;
break;
case 1:
re = c + c;
break;
case 2:
re = 4;
break;
case 3:
re = c * c;
break;
case 4:
re = c + c + 5;
break;
case 5:
re = c;
break;
default:
re = -1;
break;
}
system("pause");
return 0;
}
然后我們查看它的反匯編:
mov eax,dword ptr [ebp-0Ch]
mov dword ptr [ebp+FFFFFF20h],eax
cmp dword ptr [ebp+FFFFFF20h],5
ja 0047255F
mov ecx,dword ptr [ebp+FFFFFF20h]
jmp dword ptr [ecx*4+004725C8h]
ebp-0Ch
就是c
的地址,它先比較這個東西是否大於5
,如果大於直接到轉到0x0047255F
這個地址,也就是default
語句,看來編譯器還是挺“聰明的”。然而最“聰明”的不在這里,而是jmp dword ptr [ecx*4+004725C8h]
這句匯編。讓我們看看0x04725C8
這個地址到底存儲的是什么東西:
首先打開內存窗口,輸入那個地址,然后在內存窗口顯示右鍵選中四個字節整數
,沒有文本
,十六進制顯示
即可。得到如下圖結果:
如果你細心的話,你會發現這里面存儲的都是每個case
的地址,被稱為地址表。我只需計算出一次結果,就可以跳轉到我需要的位置。
咱們舉的例子是情況連續的時候,如果不連續但差距不算太大呢,我們嘗試把case 3
刪掉,看看有什么情況出現。
- 反匯編 -
- 地址表 -
可以看出表的成員個數不變,但被刪除的case
的地址處被填充了default
語句的地址。編譯器可以通過某種推斷
來實現地址表的構建提高運行效率,但是如果每個case
沒有任何規律可言的話,那會怎么樣呢?
#include <iostream>
using namespace std;
//如果是C,請自行將頭文件包含改為 stdio.h 和 stdlib.h
//將 cout 改為 printf_s(可以 printf,但微軟編譯器編譯會報錯,自行科普)
//將 cin 改為 scanf_s(可以 scanf,但微軟編譯器編譯會報錯,自行科普)
int main()
{
int c = 0;
int re = 0;
cout << "請輸入數字:" << endl;
cin >> c;
switch (c)
{
case 0:
re = -c;
break;
case 15:
re = c + c;
break;
case 200:
re = 4;
break;
case 489:
re = c + c + 5;
break;
case 542:
re = c;
break;
default:
re = -1;
break;
}
system("pause");
return 0;
}
然后看一下反匯編:
哈哈,這回編譯器“找不到頭腦了”,只能老老實實的用if-else
的樣式生成匯編了。
循環語句
循環語句應該是編程中經常會用到的語句。所有的形式示例如下:
for語句
for (int i = 0; i < 5; i++)
{
//do something
}
while語句
int i;
do
{
//do something
} while (i<5);
do語句
int i;
while (i<5)
{
//do something
}
在匯編層面,所有循環到匯編的本質都是一樣的,下面我們用代碼進行驗證:
#include <iostream>
//如果是C,請自行將頭文件包含改為 stdio.h 和 stdlib.h
int main()
{
int c = 0;
//for循環
for (int i = 0; i < 5; i++)
{
c++;
}
//do循環
int i = 0;
do
{
c++;
i++;
} while (i < 5);
//while循環
i = 0;
while (i < 5)
{
c++;
i++;
}
system("pause");
return 0;
}
然后編譯運行,查看它的反匯編,結果如下:
- for循環 -
- do循環 -
- while循環 -
跳轉語句
break
、continue
、goto
被我統稱為跳轉語句。break
和continue
語句經常在循環語句和switch
語句出現,經常和if
配套以判斷是否不符合循環條件跳出而使用。翻譯到匯編層面,它不過就是一條jmp
指令,switch
語句的已經體現了。goto
語句翻譯到匯編也是一條jmp
指令,但如果處理不善,就會打亂程序執行流程出現不太可預測的結果,不太建議使用。那我們做一個循環語句的,其他自行探索實驗,代碼如下:
#include <iostream>
using namespace std;
//如果是C,請自行將頭文件包含改為 stdio.h 和 stdlib.h
//將 cout 改為 printf_s(可以 printf,但微軟編譯器編譯會報錯,自行科普)
int main()
{
for (int i = 0; i < 10; i++)
{
label:
if (i==2)
continue;
if (i == 7)
goto label;
if (i==8)
break;
}
system("pause");
return 0;
}
for each語句
經查閱,這個語句僅在微軟的編譯器里面有。所以本人還是略微做一下實驗,來看看for each
語句到底為我們做了什么東西。在實驗之前,需要通過項目屬性頁
-C/C++
-語言
來關閉符合模式
,代碼如下:
#include <iostream>
using namespace std;
//如果是C,請自行將頭文件包含改為 stdio.h 和 stdlib.h
//將 cout 改為 printf_s(可以 printf,但微軟編譯器編譯會報錯,自行科普)
int main()
{
int nums[] = { 1,2,3,4,5,6 };
int num = 0;
for each (int var in nums)
{
num += var;
}
system("pause");
return 0;
}
然后看一下反匯編:
一個簡簡單單的for each
卻為我們生成了好幾行代碼,剩下的還請自行探索。