51單片機(jī)編程進(jìn)階篇 #1簡(jiǎn)短的代碼未必是高效的代碼

讀大學(xué)時(shí),我的C語(yǔ)言老師課間喜歡和我們吹牛,動(dòng)不動(dòng)就說(shuō)老子當(dāng)年寫(xiě)過(guò)幾萬(wàn)行的代碼。才讀大學(xué)的我,被老師忽悠的一愣一愣,心想我什么時(shí)候也學(xué)有所成,寫(xiě)出幾萬(wàn)行的代碼,月薪過(guò)萬(wàn)不是夢(mèng)??!

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

1 自增、自減操作

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

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

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

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

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

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

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

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

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

△代碼②

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

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

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

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

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

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

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

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

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

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

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

圖2 代碼3

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

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

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

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

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

圖3 代碼4

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

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

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

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

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

3.簡(jiǎn)短的語(yǔ)句未必高效

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

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

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)時(shí)我的整個(gè)程序是有問(wèn)題的,找朋友是請(qǐng)教一下,給我看看代碼可能哪里出錯(cuò)。結(jié)果看到上面這個(gè)函數(shù)后(這是精簡(jiǎn)后的示例,不是實(shí)際代碼,但結(jié)構(gòu)相同),就開(kāi)始噴了。

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

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

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

我:??????

后來(lái)再也沒(méi)找他請(qǐng)教過(guò)問(wèn)題,因?yàn)槲覀兊乃酵耆辉僖粋€(gè)檔次,和這種人討論編程簡(jiǎn)直就是侮辱自己的智商。

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

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();  
    }           
}

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

圖4

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

圖5

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

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

圖6

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

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

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

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

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

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

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

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

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