51單片機編程進階篇 #1簡短的代碼未必是高效的代碼

讀大學(xué)時,我的C語言老師課間喜歡和我們吹牛,動不動就說老子當(dāng)年寫過幾萬行的代碼。才讀大學(xué)的我,被老師忽悠的一愣一愣,心想我什么時候也學(xué)有所成,寫出幾萬行的代碼,月薪過萬不是夢啊!

然而事實是,工作接觸了一些大神和工程師后才知道,很多人雖然真的可以寫出成千上萬行代碼,但不意味著他們多有水平。那些動不動就吹噓自己寫過多少代碼的人,往往是沒什么事跡可吹。真正的大神,都以代碼簡短、高效、穩(wěn)定、可讀性強為榮。今天要說的是代碼的簡短和高效,很多人往往認為簡短的代碼就代表高效,殊不知,這和那些吹噓自己寫過多少萬代碼的人一樣膚淺。下面我會通過單片機編程中常見的代碼說明,簡短的代碼未必是高效的代碼。

1 自增、自減操作

我們經(jīng)常會看到這些寫法:
i+ +
i- -
同樣的,我們還知道這種寫法:
i = i+1
i = i-1
那么要問,哪種寫法更好呢?大部分人會說i+ +這種,為什么呢?他們往往會說,i+ +這種寫起來簡單,高效。i+ +的執(zhí)行效率比i = i+1要高。
書寫效率的優(yōu)勢是肯定的,敲3次鍵盤和敲5次鍵盤當(dāng)然不一樣,關(guān)于輸入效率,其他文章里我會提到,這里暫且不談,此處只討論代碼效率。
有人說,i+ +只操作一個變量,i = i+1執(zhí)行了+1后,還需要賦值一次,當(dāng)然效率就低。這個觀點看起來沒什么問題,卻犯了形式主義錯誤。
大多數(shù)人的看法,都是通過語言本身分析。也就是說,i = i+1給人的直覺,是先執(zhí)行了i+1,再賦值,是兩個動作;而i++無論怎么看,都是一個動作,但這并不是事實。事實是,在Keil-C51開發(fā)環(huán)境下,他們都會被編譯為同一條指令,如圖1所示。

圖1 i++和j=j+1的編譯結(jié)果

紅色框線是i+ +編譯后的匯編指令,藍色框線是j = j+1編譯后的匯編指令。就算不懂匯編也可以看出,都是INC指令。0x08和0x09是變量i和j的物理地址,僅僅是地址不同。這意味著,不管你是寫i+ +還是j = j+1,他們都被編譯為同一條指令被CPU執(zhí)行。

結(jié)論:i+ +i = i+1這兩種寫法在單獨使用時,執(zhí)行效率是完全一樣的。出于個人習(xí)慣,寫哪種都可以。

2、較少的行數(shù)效率就高

有一種看起來簡單高效的寫法,蠱惑了很多初學(xué)者多年,即:if(i++ == x)if(++i == x)

很多老師都會花很多篇幅講他們的區(qū)別和用法。然而在Keil-C51編譯環(huán)境中,糾結(jié)這兩種用法是蠢的事情。本文為了更好的說明,我還是先解釋一下if(i++ == x)if(++i == x)的區(qū)別。
△代碼①

if(i++ == 2)
    {
        k=k+5;
        //用戶代碼
    }

代碼①中,當(dāng)i的值等于3時,執(zhí)行括號里面的代碼。

△代碼②

if(++i == 2)
    {
        k=k+5;
        //用戶代碼
    }

代碼②中,當(dāng)i的值等于2時,執(zhí)行括號里面的代碼。

很多人知道這個結(jié)果,并以知道這個“技巧”為榮。這就苦了很多初學(xué)者,兩種寫法得到不同的結(jié)果,導(dǎo)致調(diào)試很久。如果初學(xué)者問有經(jīng)驗的人,他們大多是這么解釋:

if(i++ == 2)是先執(zhí)行i++,讓在執(zhí)行if條件語句判斷,而if(++i == 2)是先引用i的值判斷,再執(zhí)行++i。有道理嗎?有,因為編譯器確實是這么編譯的。有興趣的讀者可以自己debug試一試。所以呢,很多人寫程序的之后,會巧用這個技巧,寫出自以為高明的代碼。實際上并不怎么高明,我來解釋一下為什么。

我們寫的代碼,大多數(shù)是要拿給別人看的,除非你是初學(xué)者,寫的代碼到處是拼音、錯拼的英文,不規(guī)范的縮進。但作為老師、硬件開發(fā)者,代碼除了要達到目的,還要兼顧維護性??删S護性包含的內(nèi)容很多,最直觀的就是閱讀性,所以先說一下閱讀性。

對于初學(xué)者,最開始是先接觸i++、++ii = i+1這種基礎(chǔ)內(nèi)容。我們也很容易在老師那知道,i++++i都是自增操作。所以直覺告訴我們,前文提到的兩段代碼執(zhí)行結(jié)果應(yīng)該相同,然而測試后才發(fā)現(xiàn)不同。這里說直覺,是的,人就是無法避免直覺性的判斷,雖然很多時候我們被直覺欺騙。就比如前文提到的i++i = i+1,很多是根據(jù)直覺判斷誰的效率高,但沒有任何事實依據(jù)。我不反對老師講課的時候講解if(i++ == x)if(++i == x)的區(qū)別,但我希望最后加上一句:不要糾結(jié)這些用法,忘記了網(wǎng)上搜索一下即可。因為你們可以把代碼寫為這種格式:

    unsigned char i,k;
    i = i + 1;      
    if(i == 2)
    {
        k = k + 5;
    }

這樣不管寫為i++、++i 還是i = i + 1;都可以實現(xiàn)預(yù)期結(jié)果。這種寫法,讀者不需要考慮if(i++ == x)if(++i == x)的區(qū)別。我的代碼里,都是這種寫法,有朋友看了后對此嗤之以鼻。他說,說明明可以寫為if(++i == 2)這種格式,一行不比兩行效率高嗎?一行不比兩行看起來簡潔嗎?對于這種人,最開始做朋友沒什么,時間久了了,類似“高明”的建議多了,我就敬而遠之。

我們來看看所說的1行是不是真的比2行高效,先設(shè)計一段2行代碼:
△代碼③

    unsigned char i,k;
    ++i;                
    if(i == 2)
    {
        k = k + 5;
    }

Debug結(jié)果如圖2所示

圖2 代碼3

++i和if(i == 2)編譯后匯編語句如下:

INC     0x08
MOV     A,0x08
CJNE    A,#0x02,C:0010

讀者可能說,我不懂匯編啊,沒關(guān)系,姑且先記下這3行匯編,我們看一看下面的代碼。
△代碼④

    unsigned char i,k;
    if(++i == 2)
    {
        k = k + 5;
    }

Debug結(jié)果如圖3所示

圖3 代碼4

紅色框線內(nèi)是if(++i == 2)編譯后的匯編語:

INC    0x08
MOV    A,0x08
CJNE   A,#0x02,C:0010

這三行是不是似曾相識呢?沒錯,和代碼③編譯的結(jié)果完全相同,這也意味著,不管怎么寫,編譯后的匯編語句相同,執(zhí)行效率自然也相同。所以不存在后者比前者高效的結(jié)論。

至于閱讀效率,我只談?wù)勎业膫€人感受。當(dāng)寫為代碼③的格式時,不管你是老師授課,還是研發(fā)者,當(dāng)把代碼給他人參考、修改,只要讀者不是太離譜,都看得懂。但是寫為代碼④的格式后,至少初學(xué)單片機C編程的人,閱讀起來會產(chǎn)生麻煩,不幸這個人還很健忘,忘記了if(++i == 2)if(i++ == 2)的區(qū)別,沒準老師還沒講過區(qū)別,那么幾乎都會出現(xiàn)理解問題。

相比Python這種優(yōu)雅的語言,就取消了自增自減操作,這不是倒退,這是一種進步,至少,沒有了自增自減,程序設(shè)計者不需要再糾結(jié)上述困擾。

3.簡短的語句未必高效

我寫程序的時候,switch case語句和if語句都經(jīng)常用到。早期的開源設(shè)計代碼中,幾乎都是使用switch case,后來逐漸的使用ifif elseif。具體原因我會在其他章節(jié)專門介紹這兩種語句的時候詳細說明。本文著重說一下兩個語句效率問題。

朋友、同事之間互看代碼是常有的事情,也是一個朋友,看到我一段代碼里是這樣的:

unsigned char test1()
{
    unsigned char m=2,n;

    if(m == 0)
    {
         n = m * 10 + 1;    
    }
    if(m == 1)
    {
         n = m * 10 + 1;    
    }
    if(m == 2)
    {
         n = m * 10 + 1;    
    }
    return n;
}

當(dāng)時我的整個程序是有問題的,找朋友是請教一下,給我看看代碼可能哪里出錯。結(jié)果看到上面這個函數(shù)后(這是精簡后的示例,不是實際代碼,但結(jié)構(gòu)相同),就開始噴了。

朋友:你這種水平的代碼,我看都懶得看,你先優(yōu)化一下代碼,我再給你檢查問題。

我:哪里體現(xiàn)出水平有問題呢?

朋友:代碼講究的是簡短、高效,你這么多if,看著都難受。你這里如果用switch case語句,看著多舒服,執(zhí)行起來效率也更高。

我:??????

后來再也沒找他請教過問題,因為我們的水平完全不再一個檔次,和這種人討論編程簡直就是侮辱自己的智商。

實際情況真的有如我朋友所言嗎?switch case語句就真的比if這種高效嗎?這次我們換個方式看一下效率,看看switch case語句是否在效率上比if語句更快。測試代碼如下:

unsigned char test1()
{
    unsigned char m=2,n;

    if(m == 0)
    {
         n = m * 10 + 1;    
    }
    if(m == 1)
    {
         n = m * 10 + 2;    
    }
    if(m == 2)
    {
         n = m * 10 + 3;    
    }
    return n;
}

unsigned char test2()
{
    unsigned char m=2,n;
    switch (m)
    {
        case 0 : n = m * 10 + 1; break;
        case 1 : n = m * 10 + 2; break;
        case 2 : n = m * 10 + 3; break;
        default: break;
    }
    return n;
}

void main()
{
    unsigned char tmp;
    while(1)
    {
        tmp = test1();
        tmp = test2();  
    }           
}

兩個測試函數(shù)(test1()test2())的功能是一樣的,我們通過debug功能看一下效率問題。Debug調(diào)試結(jié)果如圖4所示。

圖4

紅色箭頭所指的“sec”為執(zhí)行代碼所經(jīng)過的時間,在執(zhí)行tmp = test1()之前,時間已經(jīng)經(jīng)過了0.00019450秒,記為t0。黃色箭頭所指的即為即將執(zhí)行的代碼,按下“F10”鍵(或單擊“Step Over”)后,如圖5所示。

圖5

執(zhí)行tmp = test1()后,時間變?yōu)?.00020500秒,記為t1。

此時即將執(zhí)行的代碼為tmp = test2(),再次按“F10”鍵,時間變?yōu)?.00021550秒,記為t2。如圖6所示。

圖6

根據(jù)兩次執(zhí)行結(jié)果,可得到:
執(zhí)行tmp = test1()的時間 = t1 - t0 = 0.00001050秒
執(zhí)行tmp = test2()的時間 = t2 - t1 = 0.00001050秒

至此,我們可以說,把test1()里面的if語句改為switch case語句并沒有提高效率,反倒是完全一樣。

讀者們,你們能根據(jù)前文的方法,發(fā)現(xiàn)為什么兩種寫法效率一樣嗎?有興趣的讀者可以把if語句改為if else語句與switch case語句再次對比,能否發(fā)現(xiàn)什么

總結(jié):if語句改為switch case語句,確實讓代碼行數(shù)少了很多,尤其是在出現(xiàn)多個if的時候,但“看起來”簡短的語句,也不能代表代碼效率的高效。這里還要補充一點,雖然if語句行看起來沒有switch case語句簡短,但閱讀效率未必就低下,同時,如果需要多重判斷(往往上百次上千其次),switch case語句也會顯得臃腫,那種情況下,會有更好的寫法,我會在其他文章詳細介紹。

** 上述測試環(huán)境為Keil4 C51 @12Mhz **

我的那些“朋友”,至今也堅信i++i=i+1高效,switch caseif高效。他們都犯了形式主義錯誤,很多單片機C語言老師或工程師,往往把其他編譯環(huán)境的結(jié)論帶到單片機領(lǐng)域,在他們眼中,并無不可,結(jié)果卻誤導(dǎo)了很多人。我在學(xué)習(xí)的過程中,也經(jīng)常被各種人被所謂的經(jīng)驗誤導(dǎo)。學(xué)習(xí)編程,就是要敢于質(zhì)疑權(quán)威,這些權(quán)威可以是老師,你的師兄,也可以是我,只有這樣,才能學(xué)到真正的知識。

作者水平有限,編寫過程中難免出現(xiàn)不當(dāng)之處,還望讀者諸君不吝賜教,或許您有好的建議,歡迎與我聯(lián)系QQ:136678431,作者將報以實質(zhì)性獎勵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容