新聞中心

EEPW首頁 > 牛人業話 > 關于C語言枚舉類型不得不說的故事

關于C語言枚舉類型不得不說的故事

作者:馬步時間:2020-05-26來源:電子產品世界收藏

經濟學家說過,路邊是不會有100元的,但是如果有,你還是要撿起來。

本文引用地址:http://www.mzugvc.icu/article/202005/413512.htm

同理,在貌似萬物免費的網絡時代,你是很難找到有針對性的好資料的,但是如果有,希望你能認真學習吸收。

比如筆者今天寫的這一篇:)

今天這篇文章要分享兩個案例,第一個案例關于枚舉,第二個案例也是關于枚舉。

照舊例,先來幾句簡單的照本宣科。用于針對某一類對象定義一個集合,根據該類對象的實際意義將集合中的元素逐一列舉出來,然后用實際取值為整數(枚舉值)的文本式變量描述這些元素。

這些枚舉值相當于一種助記符,可以提供對某一類對象更加貼近實際的描述,所以不僅能夠增加程序的可讀性,還能幫助碼農們分別并記憶它們。當然,在具體的編程活動中,枚舉型也會暫時把碼農從枯燥的計算機世界解脫出來,找回一點人間煙火的感覺。

科普完畢,大家可能開始納悶了。既然從數學概念上來理解,枚舉定義了一個“集合”,用整型取值來表示集合中的“元素”,邏輯上如此清晰而且簡單,這還可能出什么問題?

你想,平地里可以起驚雷,陰溝里也會翻了船,編程寫出個bug來,難道不是意料之外、情理之中的事情嗎?

只不過,我始終搞不清楚,編程時,到底一帆風順無驚無喜是幸福的,還是遇到問題百轉千回更幸福?

說到幸福,我不禁想起范偉的一段經典臺詞,腦袋大脖子粗的范偉端著個大臉盤子,無神的眼睛里透露著看破紅塵的滄桑,慢條斯理地回答:“什么是幸福?幸福就是我餓了,看別人拿個肉包子,那他就比我幸福;我冷了,看別人穿了一件厚棉襖,他就比我幸福;我想上茅房,就一個坑,你蹲那了,你就比我幸福?!?/p>

同樣是簡單的枚舉,你用時沒碰到問題,而我碰上了,你說咱倆到底誰比誰幸福?

道家有一句很玄妙的話:天下本無事,庸人自擾之!

堅定地秉持唯物主義的四有青年們對這句話當然是嗤之以鼻孔兼鼻毛的。

你見或者不見,事兒就在那里,不來不去,但是按照老莊的思想,合著是我們自己沒事找事了?

對此等斷語,筆者只能微微一笑很傾城,接著苦笑很悲情了。因為我遇到的枚舉問題就是自己瞎搞出來的。

本來,同事小周給我的代碼里有這么兩段代碼:

void SendI2cAck(void)

{

SetSdaDir(IO_DIR_OUTPUT);

SetSdaLow();

ToogleScl();

}

void SendI2cNak(void)

{

SetSdaDir(IO_DIR_OUTPUT);

SetSdaHigh();

ToogleScl();

}

明眼人一眼就看出來了,盡管每段代碼都很簡單,完全沒有必要改寫,但是由于這兩段代碼的重復度很高,它們完全可以改寫成一個帶參量的函數。

尤其對我們這種對代碼清理和重構有著偏執型沖動的人來說,讓我們不重構簡直比殺了我們還難受,此時不改,更待可時?

于是我三下五除二,把代碼改成了下面的樣子:

void i2c_ack(e_I2cAck ack)

{

SetSdaDir(IO_DIR_OUTPUT);

if(I2C_ACK == ack){

SetSdaLow();

}else{

SetSdaHigh();

}

ToogleScl();

}

在這里,筆者定義了一個

typedef enum{

I2C_ACK = 0,

I2C_NAK = 1

}e_I2cAck;

然后,因為鬼才知道的原因,筆者給出了如下函數聲明,也在不經意間埋下了一顆炸彈。

void i2c_ack(uint8_t ack);

看到這里,大咖們可能在捏著下巴上唏噓的胡茬子會心一笑了,但是小白們也許還是不知所以。

盡管函數的聲明誤寫成了i2c_ack(uint8_t ack),但是它的定義i2c_ack(e_I2cAck ack)還是對的,在調用函數傳遞函數參量的過程中,傳進去的I2C_ACK難道不還是0,I2C_NAK不還是1吧?

筆者也是這么想的,當然,剛開始的時候,我根本沒有發現把聲明寫錯的“筆誤”。

不過,埋下的炸彈終會暴雷。由于重構后的程序運行不正常,我很快發現了聲明和定義不一致,但是,so what?我依然不得要領,于是只好架上仿真器單步調試,看看到底會發生什么。

我追蹤調試到調用i2c_ack的地方,眼見著把I2C_ACK=0傳了進去,到了函數里面后,竟然沒有執行if(I2C_ACK == ack)這個分支。于是我試著添加了一個uint16_t型的臨時變量,將函數參量賦值給它。

不看不知道,一看嚇一跳,傳遞進來的參量竟然成了0x5A00。

追蹤到這里,又查閱了相關資料后,我似乎有些開竅了。

盡管8位整型便可以涵蓋這次枚舉定義中的最大值,但是的尺寸是16位,而非所想象的8位。

這樣一來,如果函數聲明中的參量是16位,那么,在參量傳遞時,傳遞進來的枚舉類型的I2C_ACK會被處理成16位整型的‘0’,函數會按照‘0’分支進行正確的處理。但是,由于函數聲明中的參量是8位,所以,實際上傳遞進來的枚舉類型的I2C_ACK只取了1個8位整型的‘0’,進入函數內部后,它又會被擴展成16位整型,而函數內部的變量是局部變量,地址空間都在stack里面,它擴展時會采用相鄰的高位地址來填充該16位整型的高8位。這樣,在傳遞0時,數據低八位依然是0,但是高八位就不一定了。

本來不改程序,還不會遇到這些問題,看看,是不是天下本無事,庸人自擾之?

千百年來,多少人苦苦思索,到底是什么力量,掌握著我們的命運,讓我們經歷痛苦和歡樂?

現在我明白了,生命不息,折騰不止,正是這種沒事找事瞎折騰的力量主宰了我們的喜怒哀樂呀!

筆者分享的第二個關于枚舉類型的案例,是更加便利地使用枚舉類型進行數組索引的一種新用法,不敢藏私,與諸君共享之。

如前所述,枚舉的一個重要作用是增加程序的可讀性,以助記符的形式幫助程序員記憶和理解代碼。比如,筆者在實現軟件定時器時(見文章《如何用單個定時器統一地實現多種定時應用》)就曾經以枚舉類型定義了軟件定時器的ID或者說軟件定時器的名稱。

為了讓讀者更加便于理解,還是要花開兩朵各表一枝,叨咕叨咕軟件定時器。

一個嵌入式產品中會有很多定時邏輯,最好也是最通用的處理方式便是設計一種結構體形式的軟件定時器,令一個軟件定時器對應一種定時邏輯,所有軟件定時器構成一個結構體數組,各種定時邏輯的實現時便是在結構體數組中的成員變量上進行處理。

在這里,以可讀性較強的枚舉類型定義軟件定時器的ID,枚舉值根據各個定時應用的具體邏輯命名,比如說,檢測輸入信號的周期性定時器INPUT_DETECT_PTMR、喂看門狗的周期性定時器FEED_WATCHDOG_PTMR、監測系統狀態的周期性定時器SYS_MONITOR_PTMR、蜂鳴器報警的多次定時器BEEPTWEET_TTMR、總線busoff后恢復通信的單次定時器BUSOFF_TTMR等。

高智商的程序猿們打眼一看,就能從枚舉值的命名上看出定時器背后的邏輯來,枚舉增強程序可讀性的功能可見一斑,但是,問題是,您老人家看明白了,單片機呢?

這么說吧,我們在用Timer[INPUT_DETECT_PTMR]處理定時邏輯時,怎么保證這個定時器節點就能具體對應到檢測輸入信號的周期性定時器嗎?

智商在線的你肯定不會因為INPUT_DETECT_PTMR這個文本化的枚舉寫得如此得昭彰就想當然地認為單片機也能“心同此心”的。實際上,如果你不做一些特殊的處理,單片機肯定不知道Timer[INPUT_DETECT_PTMR]就可以表征檢測輸入信號的周期性定時器的。

愿你三冬暖,愿你春不寒,愿你天黑有燈,下雨有傘。程序猿想和單片機接下此等心心相映的緣,需要做點編程工作,主動手拉手線牽線。

顯然,INPUT_DETECT_PTMR此類軟件定時器節點ID想在數組中充當下標使用,下標和枚舉之間要具有天然的一致性。

所幸,數組Timer[N]的下標范圍是[0,N-1]間的正整數,而整型取值正是枚舉類型的天然屬性。所以,第一步是要保證定時器枚舉也從0開始取值,然后取值依次加一,在[0,N-1]間一一占位。

第二步,在定時器數組的初始化階段,要用整數型下標進行一次for循環,將各個軟件定時器節點的ID初始化為對應的數組成員的下標,即Timer[i].timer_id = i,這里的i有三個作用,一是for循環體中的循環變量,二是數組成員下標,三是賦值給定時器ID。

在系統運行階段,引用某個軟件定時器時,以該軟件定時器對應的枚舉類型常量做為數組下標,引用以該ID標識的軟件定時器節點,即用Timer[timer_id]直接尋址具體的軟件定時器,

這里有一個好處是,避免了以整型變量為下標引用定時器時需要查找該定時器節點在軟件定時器數組中對應的下標的繁瑣,而且提高了程序的可讀性。

其中妙處,你品,你仔細品!



關鍵詞: C語言 枚舉類型

評論


技術專區

關閉
下载优部怎么才能赚钱 今日股市行情 融券的股票从哪里来 2019年甘肃快3和值走势图 江西多乐彩 娱乐电子棋牌APP 重庆快乐10分玩法 王中王三期内必出四肖 上期算出下期五行公式 四肖八码期期准香港精选资料 股票配资广告语