當(dāng)前位置:首頁 > 公眾號(hào)精選 > 嵌入式微處理器
[導(dǎo)讀]嵌入式系統(tǒng)應(yīng)用領(lǐng)域千差萬別、他們對(duì)嵌入式系統(tǒng)的要求和側(cè)重點(diǎn)不盡相同,(如工業(yè)控制特別強(qiáng)調(diào)可靠性), 但基本要求嵌入式系統(tǒng)功能強(qiáng)大、性能穩(wěn)定、工作可靠。但這3點(diǎn)不是相輔相成的,而是互相之間有矛盾的。

自從40多年前嵌入式系統(tǒng)誕生以來,隨著技術(shù)的發(fā)展和需求的變化,嵌入式系統(tǒng)軟件就在嵌入式系統(tǒng)中越來越重要?,F(xiàn)在,甚至一些嵌入式系統(tǒng)硬件一模一樣,僅僅是軟件不同,就是不一樣的產(chǎn)品(如交換機(jī)和路由器)。

?
嵌入式系統(tǒng)應(yīng)用領(lǐng)域千差萬別、他們對(duì)嵌入式系統(tǒng)的要求和側(cè)重點(diǎn)不盡相同,(如工業(yè)控制特別強(qiáng)調(diào)可靠性), 但基本要求嵌入式系統(tǒng)功能強(qiáng)大、性能穩(wěn)定、工作可靠。但這3點(diǎn)不是相輔相成的,而是互相之間有矛盾的。
?
嵌入式系統(tǒng)的功能、穩(wěn)定性、可靠應(yīng)與嵌入式系統(tǒng)的硬件、軟件都有關(guān)系。本文僅討論版嵌入式系統(tǒng)軟件的可靠性設(shè)計(jì)問題,因此假設(shè)嵌入式系統(tǒng)的硬件是穩(wěn)定可靠的。盡管一些應(yīng)用可以在不可靠的硬件上通過軟件設(shè)計(jì)獲得可靠的產(chǎn)品(如U盤,NAND FLASH是一個(gè)不可靠的存儲(chǔ)介質(zhì),但通過軟件設(shè)計(jì),可以得到可靠的存儲(chǔ)設(shè)備,硬盤更是如此),但這不在本文的討論范圍之內(nèi)。
?
可靠性與穩(wěn)定性之間的關(guān)系
?
定律1:越簡(jiǎn)單的東西越容易做得可靠
相對(duì)錘子來說,機(jī)械手表足夠復(fù)雜。如果讓一個(gè)錘子和一個(gè)機(jī)械手表都從10層樓高處掉到普通水泥地面,哪個(gè)損壞的可能性更大?
?
當(dāng)然,如果花費(fèi)大的代價(jià),如使用最好的材料,并增減減震系統(tǒng),機(jī)械手表甚至可以做到錘子摔壞了而手表不壞。不相信?飛行員從幾萬米高空掉下來不受傷的比比皆是(當(dāng)然有降落傘啦)。
?
從上述說明可知,簡(jiǎn)單的東西很容易做得高可靠,但復(fù)雜的東西要做高可靠花費(fèi)的代價(jià)就高多了。這是普遍原則,對(duì)于嵌入式軟件也適用。既然如此,那為什么人們還要做復(fù)雜的東西呢?這就涉及第二定律了。
?
定律2:越復(fù)雜的東西越容易做得穩(wěn)定
記得大學(xué)剛?cè)雽W(xué)時(shí)有軍訓(xùn),最后一項(xiàng)是打靶。本班奉命在打靶的前一天下午擦拭打靶用得半自動(dòng)步槍,具體型號(hào)記不得了,但肯定是中國建國后早期生產(chǎn)的。在擦拭前教官給我們講注意事項(xiàng),其中有一句是這樣的:“一個(gè)人擦一把槍,不要把零件搞混,否則裝不上的?!币簿褪钦f,同樣型號(hào)的兩把槍,同一個(gè)零件不能互換!只是因?yàn)榻▏跗诘臉尪际鞘褂煤?jiǎn)單的工具制造的,零件的尺寸、質(zhì)量都不穩(wěn)定,而一把槍上一些零件間的公差要求較小,只好用人工的方法篩選能夠互相配合的零件組裝成成品。這樣,由于產(chǎn)品零件的不穩(wěn)定,造成了同一個(gè)型號(hào)的產(chǎn)品的零件互不通用。再看一些現(xiàn)在的槍支,不同型號(hào)的槍支60%零件可以互換是很正常的,這有設(shè)計(jì)的原因,同時(shí)也要?dú)w功于制造工具足夠精密復(fù)雜,足以制造尺寸質(zhì)量足夠穩(wěn)定的零件。
?
嵌入式系統(tǒng)軟件也是這樣。我們的代碼越寫越大,越寫越復(fù)雜,很大程度不就是讓軟件在各種情況下都能夠穩(wěn)定運(yùn)行嗎?
?
定律3:每個(gè)系統(tǒng)有一個(gè)最小的復(fù)雜度
一般普通的錘子必須有一個(gè)錘柄和一個(gè)錘體,錘柄最簡(jiǎn)單估計(jì)是圓柱體了,錘體也一樣。似乎最簡(jiǎn)單的錘子就是由兩個(gè)圓柱體組成了,筆者想象不出更簡(jiǎn)單的錘子。而要把錘子做復(fù)雜一些很容易,方法很多,例如在錘子上鑄龍雕鳳。
?
也就是說,在相同的功能與穩(wěn)定性的前提下,每個(gè)系統(tǒng)有一個(gè)最小的復(fù)雜度。錘子的功能是敲打東西,僅僅是這個(gè)功能的話,僅需要一個(gè)垂體即可,但那樣容易傷到人的手(穩(wěn)定性不好),所以需要一個(gè)錘柄。嵌入式系統(tǒng)軟件也是如此。
?
結(jié)論
由上面3條定律可知,系統(tǒng)的穩(wěn)定和可靠之間有一定的矛盾:提高穩(wěn)定性容易實(shí)現(xiàn)的方式是降低系統(tǒng)的復(fù)雜度,這又往往降低了系統(tǒng)的穩(wěn)定性。同樣,提高系統(tǒng)的穩(wěn)定性又容易降低系統(tǒng)的可靠性,要穩(wěn)定和可靠都高就需要花費(fèi)比較大的代價(jià)。
?
功能與可靠性、穩(wěn)定性之間的關(guān)系
?
由上可知,系統(tǒng)的功能與可靠性、穩(wěn)定性之間不是孤立的,而是互相聯(lián)系互相制約的,下面詳細(xì)分析。
?
定律1:功能的增加是依靠復(fù)雜度的增加而增加的
大家知道,普通的錘子只能錘東西,現(xiàn)在需要增加拔釘子的功能,錘體的一端需要改變形狀,很顯然更難制造了(復(fù)雜度增加了)。錘子功能增加了,可是也更難使用也更容易損毀了(錘子拿反了,用拔釘子的一面錘東西……)。
?
復(fù)雜度增加了,要保證同樣的可靠性就需要花費(fèi)更多的代價(jià),顯然功能和可靠性也是一對(duì)矛盾。
?
定律2:功能的增加可能造成單個(gè)功能的復(fù)雜度的減少
大家可以找一個(gè)目前市場(chǎng)上可以買到的最好的拍照手機(jī),和一個(gè)普通的數(shù)碼相機(jī),比較它們的拍照效果??梢钥隙?,數(shù)碼相機(jī)的效果更好。原因是拍照手機(jī)由于種種限制,不能把其集成的數(shù)碼相機(jī)功能做得與普通數(shù)碼相機(jī)一樣復(fù)雜(鏡頭不夠精密、閃光燈只能用LED或低擋的氖燈、感光元件也只能用簡(jiǎn)單的),當(dāng)然穩(wěn)定性要差一些了。對(duì)于嵌入式軟件也是如此,受限于存儲(chǔ)空間的大小、人機(jī)接口等,嵌入式軟件的往往只能簡(jiǎn)化各個(gè)功能代碼才能把它們集成在一起。
?
復(fù)雜度降低了,要保證同樣的穩(wěn)定性就需要花費(fèi)更多的代價(jià),所以保證同樣的穩(wěn)定性甚至是不可能完成的任務(wù),顯然功能和穩(wěn)定性也是一對(duì)矛盾。
?
結(jié)論
由上面2條定律可知,系統(tǒng)的功能和系統(tǒng)的穩(wěn)定、可靠之間有一定的矛盾。要功能多又要穩(wěn)定可靠就需要花費(fèi)比較大的代價(jià)。
?
增加嵌入式系統(tǒng)軟件的可靠性和穩(wěn)定性的有效方法
?
優(yōu)化系統(tǒng)框架設(shè)計(jì)可以提高系統(tǒng)的穩(wěn)定性和可靠性
在一定的穩(wěn)定性和可靠性的基礎(chǔ)上,一個(gè)系統(tǒng)有一個(gè)理論上最小的最小復(fù)雜度,但在實(shí)際上要達(dá)到這個(gè)最小復(fù)雜度是不可能的。在實(shí)際工作中,往往如在錘子上雕花一樣,增加了復(fù)雜度,不但不會(huì)提高系統(tǒng)的穩(wěn)定性,如果做得不好,反而會(huì)降低系統(tǒng)的穩(wěn)定性。
?
系統(tǒng)的復(fù)雜度的增加,要保持原有的可靠性更困難,對(duì)提高系統(tǒng)可靠性沒有任何幫助。
?
想要花費(fèi)比較小的代價(jià)提高系統(tǒng)的穩(wěn)定性和可靠性,比較好的辦法就是減少系統(tǒng)不必要的復(fù)雜度。而對(duì)系統(tǒng)復(fù)雜度影響最大的就是系統(tǒng)框架,一個(gè)好的系統(tǒng)框架能夠抑制系統(tǒng)復(fù)雜度的不必要的增加,并且在系統(tǒng)功能變化時(shí)對(duì)已存在的功能模塊的影響降到最低。
?
這樣,提高系統(tǒng)的穩(wěn)定性和可靠性所花費(fèi)的代價(jià)就較低,間接提高了系統(tǒng)的穩(wěn)定性和可靠性。
?
穩(wěn)定可靠來源于嚴(yán)格的測(cè)試
人永遠(yuǎn)不能完全了解世界,因此設(shè)計(jì)系統(tǒng)時(shí)不可能把所有的情況都考慮到。因此,穩(wěn)定可靠不是嘴說出來的,也不能僅通過分析系統(tǒng)設(shè)計(jì)而來確定。
?
提高穩(wěn)定性的第二步來自嚴(yán)格的測(cè)試,包括先期的設(shè)計(jì)人員自己測(cè)試和中后期的第三方測(cè)試。在測(cè)試中發(fā)現(xiàn)了問題就必須修改設(shè)計(jì)并重新測(cè)試。如此反復(fù),直到在一定的時(shí)間內(nèi)測(cè)試不出問題。
?
穩(wěn)定可靠有賴于時(shí)間的檢驗(yàn)
產(chǎn)品經(jīng)過嚴(yán)格的內(nèi)部測(cè)試和小批量試產(chǎn)并提供給友好顧客使用后(外部測(cè)試),終于大批量上市了。但即使這樣,世界級(jí)的大公司也會(huì)出現(xiàn)產(chǎn)品大規(guī)模召回的現(xiàn)象,為什么?
?
前面說過,人永遠(yuǎn)不能完全了解世界,因此再嚴(yán)格的測(cè)試也不可能模擬出實(shí)際使用過程中的所有情況。這樣,用戶使用的環(huán)境和方法與測(cè)試的環(huán)境與方法不一致時(shí),產(chǎn)品潛在的不穩(wěn)定點(diǎn)或不可靠點(diǎn)被暴露出來。如果這些不穩(wěn)定點(diǎn)或不可靠點(diǎn)是致命的,產(chǎn)品必須被召回。如果不是致命的,也需要改進(jìn)設(shè)計(jì),提高系統(tǒng)的穩(wěn)定性和可靠性。如此反復(fù)。如果系統(tǒng)大量和長時(shí)間的使用而不需要改進(jìn),說明是穩(wěn)定可靠的。
?
因?yàn)閷I(yè)所以穩(wěn)定可靠
在古代,如果您與專業(yè)打鐵匠做鐵錘,誰的產(chǎn)量和質(zhì)量穩(wěn)定可靠呢?顯然是打鐵匠。為什么?因?yàn)槟菢I(yè)余的而打鐵匠是專業(yè)的。
?
為什么專業(yè)會(huì)導(dǎo)致穩(wěn)定可靠?
?
最重要的原因是他們已經(jīng)在這個(gè)領(lǐng)域花費(fèi)了很多代價(jià)提高系統(tǒng)的穩(wěn)定和可靠性(否則就不專業(yè)了),他們與非專業(yè)的已經(jīng)不在一條起跑線上,非專業(yè)的想在短期內(nèi)超過專業(yè)的是不可能的。
?
其次,是他們對(duì)本領(lǐng)域內(nèi)的情況非常了解,制定的測(cè)試方法與實(shí)際情況符合度很高,增加了穩(wěn)定性和可靠性。
?
第三,是他們可以利用已經(jīng)經(jīng)過時(shí)間檢驗(yàn)的系統(tǒng)作為新系統(tǒng)的基礎(chǔ),甚至直接使用老系統(tǒng),不可控的復(fù)雜度增加有限,只要花費(fèi)較小的代價(jià)就可以保證系統(tǒng)的穩(wěn)定性和可靠性。
?
結(jié)論:專業(yè)分工合作是提高嵌入式系統(tǒng)軟件的最快最省方法
隨著技術(shù)的發(fā)展和社會(huì)的進(jìn)步,現(xiàn)在用戶要求嵌入式系統(tǒng)功能強(qiáng)大、性能穩(wěn)定、工作可靠。一個(gè)功能強(qiáng)大、穩(wěn)定的系統(tǒng)有比較高的復(fù)雜度,但不是所有的復(fù)雜度都對(duì)系統(tǒng)的可靠性有大的負(fù)面影響。一個(gè)經(jīng)過時(shí)間檢驗(yàn)的可靠模塊對(duì)系統(tǒng)可靠性的負(fù)面影響很小。
?
但一個(gè)強(qiáng)大的系統(tǒng)往往涉及多方面的知識(shí),很多往往還不是自己的專業(yè)范圍內(nèi),自己研發(fā)要做到可靠需要花費(fèi)的代價(jià)太大,甚至超過收益。此時(shí),尋找專業(yè)的合作伙伴提供穩(wěn)定可靠的模塊集成到自己的系統(tǒng)中,自己只做自己專業(yè)內(nèi)的部分,這樣,復(fù)雜度的各個(gè)部分對(duì)可靠性的負(fù)面影響都較少,同時(shí)整體復(fù)雜度也容易控制,產(chǎn)品可以較快的上市。
?
嵌入式系統(tǒng)軟件更加適合這種模式。這是因?yàn)檐浖且环N容易復(fù)制的東西,復(fù)制品的可靠性、穩(wěn)定性和復(fù)雜度都不會(huì)改變。專業(yè)公司的軟件模塊一般已經(jīng)被多個(gè)公司在完全不同的環(huán)境使用,其功能、穩(wěn)定性、可靠性都經(jīng)過嚴(yán)格的檢驗(yàn),不會(huì)對(duì)自己的系統(tǒng)帶來大的負(fù)面影響。多個(gè)公司使用也可以分擔(dān)軟件研發(fā)的費(fèi)用,直接使用成本較低。同時(shí),專業(yè)公司對(duì)自己所屬的領(lǐng)域非常了解,他們可以協(xié)助用戶開發(fā),更進(jìn)一步降低用戶成本。
?
所以說專業(yè)分工合作是提高嵌入式系統(tǒng)軟件的最快最省方法。

需要注意的問題

男人征服世界,女人通過征服男人來征服世界;硬件叱咤江湖,軟件通過控制硬件來統(tǒng)治江湖。當(dāng)今世界,放眼江湖,有電子的地方就有嵌入式軟件,有電子故障的地方,也就有嵌入式軟件設(shè)計(jì)缺陷的影子。我們今天就把軟件所容易犯的錯(cuò)誤和規(guī)避的方法一一羅列,并給出應(yīng)對(duì)之法。

嵌入式軟件的最大特點(diǎn)是以控制為主,軟硬結(jié)合的較多,功能性的操作較多,模塊相互間調(diào)用的較多,外部工作環(huán)境復(fù)雜容易受到干擾或干擾別的設(shè)備,且執(zhí)行錯(cuò)誤的后果不僅僅是數(shù)據(jù)錯(cuò)誤而是有可能導(dǎo)致不可估量的災(zāi)難,所以總結(jié)起來,嵌入式軟件可靠性設(shè)計(jì)需注意的問題有四個(gè)方面:

1、軟件接口

先說軟件接口中容易出問題的地方和編程人員容易犯的錯(cuò)誤。

軟件接口調(diào)用一般會(huì)有數(shù)據(jù)的賦值,賦值變量的數(shù)據(jù)類型可能會(huì)存在強(qiáng)制的數(shù)據(jù)轉(zhuǎn)換;需加以檢查。如果為了防范出問題的話,可以添加對(duì)數(shù)據(jù)范圍和數(shù)據(jù)類型的檢查。

賦值數(shù)據(jù)的數(shù)量不對(duì)路,多了少了的都不好,會(huì)出現(xiàn)意外的賦值結(jié)果,不過還好,這項(xiàng)錯(cuò)誤比較好檢查。

軟件編程中,會(huì)有對(duì)某一功能操作代碼的復(fù)用,比如對(duì)某個(gè)端口的數(shù)據(jù)檢查和控制,在整個(gè)程序中只會(huì)發(fā)生兩次,為了圖省事,可能就直接把該段代碼直接插入實(shí)際程序模塊中去了,這樣,在源程序代碼中,就出現(xiàn)了兩段完全相同,完成相同功能,只是服務(wù)于不同模塊的代碼,按道理來說,這樣設(shè)計(jì)其實(shí)也沒啥問題,是的,你沒錯(cuò),但你的行為會(huì)使別人無意中犯錯(cuò)。就像青年男女相處,女孩子純粹是想和男孩子充分享受溫馨的氣氛和心情,并不想更深入的發(fā)生什么,但女孩子邀請(qǐng)男生去的是她的家,在家里換上了家居的睡衣,窗戶緊閉,放著的還是曖昧的音樂,被男孩子半強(qiáng)迫發(fā)生后,無限哀怨地說“我沒想到結(jié)果會(huì)是這樣的”,那怪得誰來呢?在代碼方面,您的這種做法與貌似引誘男孩上鉤的少女無異。

有人會(huì)說了,我這樣寫代碼怎么就算引誘呢?原因是程序可能會(huì)升級(jí),您這幾行代碼在實(shí)際應(yīng)用過程中也不能保證是盡善盡美的,發(fā)現(xiàn)不完善的地方后,勢(shì)必會(huì)修改,如果你還能想得起來,可能不會(huì)遺漏,如果修改此代碼的是別的人,改了一個(gè)地方,別的地方?jīng)]改,是不是還留著隱患?那如何做呢?方法不難,把這段功能單獨(dú)做成一個(gè)模塊即可,對(duì)此端口的讀取和控制賦值均由此獨(dú)立模塊完成,如果數(shù)據(jù)的正確性影響大的話,還需要對(duì)端口數(shù)據(jù)的正確性進(jìn)行檢查和判斷。嵌入式軟件可靠性編程方法的四個(gè)目的是防錯(cuò)、判錯(cuò)、糾錯(cuò)、容錯(cuò)。對(duì)端口數(shù)據(jù)的判斷屬于判錯(cuò)的內(nèi)容,如果數(shù)據(jù)有錯(cuò)的話,糾錯(cuò)和容錯(cuò)的設(shè)計(jì)方法應(yīng)該不用我深入講解了吧?

2、軟硬件接口

硬件如男人,對(duì)外的執(zhí)行都靠它來實(shí)現(xiàn),一旦出現(xiàn)問題,執(zhí)行后的后果就不可控了,周總理說過“外交無小事”。但如何注意呢?

對(duì)讀進(jìn)來的硬件接口的數(shù)據(jù)要判斷其真?zhèn)危?/span>

對(duì)輸出的數(shù)據(jù)的執(zhí)行效果要檢測(cè);

對(duì)輸出的數(shù)據(jù)的可能后果要進(jìn)行預(yù)防性設(shè)計(jì),數(shù)據(jù)輸出的過程,我們從設(shè)計(jì)上要做一個(gè)分析,分析的思路是一般容易局限在穩(wěn)態(tài)過程,忽視了過渡過程。舉例說明,比如我們控制一個(gè)支路的供電,從軟件控制來說,直接給繼電器一個(gè)啟動(dòng)信號(hào),讓開狀態(tài)的觸點(diǎn)閉合就可以了,非“關(guān)”即“開”,是受控繼電器的兩個(gè)穩(wěn)態(tài)狀態(tài),但事實(shí)上,在從開到閉合的過程中,支路供電的電壓并不是一個(gè)簡(jiǎn)單0V—24V(24V為示例而已)的跳變狀態(tài),而是一個(gè)抖動(dòng),有沖擊信號(hào)的過程,這種情況在硬件上的防護(hù)是必不可少的,但在軟件上也不是可以事不關(guān)己、高高掛起的。

另外在邏輯上,宜將容易被干擾和容易產(chǎn)生的干擾控制動(dòng)作從時(shí)序上控制好,予以分開隔離。比如,控制繼電器的過程是容易產(chǎn)生抖動(dòng)尖峰脈沖而干擾數(shù)據(jù)總線和控制信號(hào)總線的,這時(shí)候從控制上,不宜同時(shí)實(shí)施數(shù)據(jù)的發(fā)送和接收工作,不宜作出其他的控制動(dòng)作,惹不起咱躲得起,躲過這一陣干擾的時(shí)候總可以了吧?

3、軟件代碼

軟件的可靠性是隨著時(shí)間的推移,可靠性逐漸增加的,這一點(diǎn)區(qū)別于電子可靠性、機(jī)械可靠性。電子可靠性服從指數(shù)分布,在整個(gè)生命周期內(nèi),其失效率為一個(gè)常數(shù);機(jī)械可靠性因?yàn)槟p、腐蝕、運(yùn)動(dòng)等因素的存在,隨時(shí)間推移可靠度會(huì)下降。因此也就有了軟件可靠性設(shè)計(jì)的一個(gè)特定規(guī)律和注意事項(xiàng)。

既然需要通過時(shí)間推移,通過不斷改進(jìn),軟件可靠性得到提升。那么軟件的可維護(hù)性就是一個(gè)大問題了。這也是為什么軟件工程管理方面特別關(guān)注軟件文檔、注釋的原因了。但做這些要求的人只是人云亦云,并不理解如此做法的真正動(dòng)機(jī)。至于注釋如何去做、變量如何命名、軟件配置管理如何操作,這里面既有很常規(guī)的方法,也有一些我們司空見慣然而是錯(cuò)誤的做法。信手舉上幾個(gè)值得注意的細(xì)節(jié)供參考。

變量定義時(shí)宜將變量類型的變量名程中體現(xiàn)于其中;如AD_result_int、Cal_result_float等。這樣為的好檢查,防止數(shù)據(jù)類型的強(qiáng)制轉(zhuǎn)換或強(qiáng)制賦值時(shí)出現(xiàn)數(shù)據(jù)類型的錯(cuò)誤;

注釋要充分;

代碼的布局風(fēng)格宜統(tǒng)一,便于閱讀查找;

不可出現(xiàn)非受控的default流程,所有數(shù)值和變量,不論是調(diào)用函數(shù)時(shí)賦予的、讀取接口讀進(jìn)來的、還是中間變量計(jì)算出來的,在應(yīng)用前都宜作數(shù)據(jù)有效性的判斷,并對(duì)判定的所有可能結(jié)果均做受控的對(duì)應(yīng)處理。

… …

關(guān)于軟件可維護(hù)性編程方法方面的文章資料在網(wǎng)上是鋪天蓋地,不予贅述,綜合采用之即可。很多文章把軟件可維護(hù)性編程規(guī)范推薦做成企業(yè)的嵌入式軟件可靠性設(shè)計(jì)規(guī)范,實(shí)在是有點(diǎn)以偏概全,有失偏頗的,用一句娛樂圈的話來說,“愛情是生活的重要內(nèi)容,但它不是生活的全部”,軟件可維護(hù)性編程方法亦然。

軟件代碼在執(zhí)行中容易出現(xiàn)的下一個(gè)問題是跑飛,程序指針受到干擾,跳轉(zhuǎn)到了一個(gè)非受控位置,執(zhí)行了不該執(zhí)行的代碼。如果執(zhí)行了不該執(zhí)行的代碼,如果在程序中加入了足夠的變量判斷、讀值判斷、狀態(tài)檢測(cè)判斷等,那倒還好了,后果也不會(huì)太嚴(yán)重,甚至最終還是可能自己跑回來的。但有一種跑飛是比較可怕的,一般我們?cè)赗OM中存放的程序目標(biāo)代碼是1-3字節(jié)的指令,就是最多3條字段的目標(biāo)碼組成了執(zhí)行動(dòng)作,如果程序指針跑飛到了某個(gè)3字節(jié)指令的第2個(gè)字節(jié)上的時(shí)候,執(zhí)行的后果是什么,可就真的沒人知道了,即使在程序上作了足夠的數(shù)據(jù)判錯(cuò)、邏輯跳轉(zhuǎn)的防范措施,結(jié)果也不會(huì)好。而且ROM一般是不可能全部都被程序代碼填滿的,總有富余空間,富余空間中的默認(rèn)內(nèi)容是啥,這些默認(rèn)字節(jié)是否也會(huì)導(dǎo)致一些操作呢?單片機(jī)中的默認(rèn)空間是0FFH,DSP的我沒查過,大家有興趣查一下,跳到這些字段里,也是容易出麻煩的。

好了,不再羅嗦,直接給出解決方法吧,就是每隔一段程序代碼或控制區(qū)域,就人為放置上幾個(gè)NOP指令,在NOP指令后放置一個(gè)長跳轉(zhuǎn)的ERR處理程序。注意NOP最少放置3個(gè),這樣任何的跑飛最多只能占用2個(gè)NOP,第三個(gè)NOP一樣還是能把程序代碼揪回來,揪回來后就執(zhí)行ERR處理程序。

如果碰到安全性、可靠性等級(jí)要求比較高的程序,推薦的處理方法可以采用熱備份的處理方法,即用兩段代碼同時(shí)執(zhí)行同一個(gè)功能,執(zhí)行的結(jié)果進(jìn)行對(duì)比,如果一致則放行通過,如果結(jié)果不一致,咋處理就看您的嘍。但是… …國人有的是辦法,為了圖省事,你領(lǐng)導(dǎo)不是要求我編熱備份程序嗎,那好,我就把原來的代碼復(fù)制一遍,重新插入到某個(gè)地方,您這和明朝時(shí)代馮保太監(jiān)(還是嚴(yán)嵩、張居正阿?拿不準(zhǔn)了,大家有興趣的翻看《明朝那些事兒》查閱下)玩的沒啥兩樣,自己寫奏章,自己給自己審批奏章。既然是備份就是為了防止一個(gè)人出問題,那最好的辦法自然是不同的人來編這段,如果原理計(jì)算方法上也不同,數(shù)據(jù)采集通道也不同,那就過年帶娶媳婦的,好上加好了。

安全性和可靠性的編程細(xì)節(jié)注意事項(xiàng)還有很多,窺一斑難見全豹呵,諸位仁兄一起努力鉆研了。

4、數(shù)據(jù)、變量

變量的定義是為的避免各種混淆,同一程序內(nèi)數(shù)據(jù)和數(shù)據(jù)的混淆、不同人讀程序時(shí)對(duì)變量理解上出現(xiàn)的二義性、視覺效果上容易出現(xiàn)的錯(cuò)誤(字母的“o”和數(shù)字的“0”,字母的“l(fā)”和數(shù)字的“1”)。這里要遵循一個(gè)“要么相同,要么迥異”的基本規(guī)則,這條規(guī)則在很多的領(lǐng)域都有應(yīng)用,用的最絕的是朱元璋,對(duì)待貪官,要么不理你,自覺點(diǎn)您貪差不多了就收手吧,您自己不收手的話,做的過了直接就殺,株連幾族,所以在明朝,朱元璋是殺人最多的皇帝;在結(jié)構(gòu)的防呆性設(shè)計(jì)上,接插件的選型也是如此,如果一個(gè)乳白色和一個(gè)淺灰色的同類接插件,最好的選擇是有很直觀的視覺差異或結(jié)構(gòu)的差異,或者干脆就是相同的,相同須基于一個(gè)前提,互換性要好。

用顯意的符號(hào)來命名變量和語句標(biāo)號(hào)。標(biāo)識(shí)符的命名有明確含義,且是完整單詞或易理解的縮寫。短單詞通過去掉“元音”形成縮寫;長單詞取頭幾個(gè)字母形成縮寫;一些單詞有公認(rèn)的縮寫。如:

Temp — tmp;
Flag — f.l.g;(*注:請(qǐng)去年中間的.號(hào))
Statistic — stat;
Increment — inc;
Message — msg。

特殊約定或縮寫,要有注釋說明。在源文件開始處,對(duì)使用的縮寫或約定注釋說明。自己特有的命名風(fēng)格,要自始至終保持一致。對(duì)于變量命名,禁止取單個(gè)字符(如i、j、k...);含義+變量類型、數(shù)據(jù)類型等,i、j、k作局部循環(huán)變量是允許的,但容易混淆的字母慎用。如int Liv_Width,L代表局部變量(Local)(g全局變量Global)、i代表數(shù)據(jù)類型(Interger)、 v代表 變量(Variable)(c常量Const)、Width代表變量的含義,這種命名方式可防止局部變量與全局變量重名。

禁用易混淆的標(biāo)識(shí)符(R1和Rl,DO和D0等)來表示不同的變量、文件名和語句標(biāo)號(hào)。

除了編譯開關(guān)/頭文件等特殊應(yīng)用,避免使用_EXAMPLE_TEST_之類以下劃線開始和結(jié)尾的定義。

全局變量是戰(zhàn)略性資源,它決定了模塊和模塊間的耦合度,需在項(xiàng)目上提升到一個(gè)足夠高的高度,慎用全局變量,不得不用的時(shí)候,要單獨(dú)為每一個(gè)全局變量編寫?yīng)毩⒌牟僮髂K或函數(shù),在修改全局變量的時(shí)候,要檢查是否有別的函數(shù)在調(diào)用它并且需要此數(shù)值保持穩(wěn)定。

對(duì)變量代表某個(gè)特定含義的時(shí)候,盡量不要僅僅用位來代表什么,比如用某變量的第零位代表某個(gè)狀態(tài)(0000 0001,其中僅用1代表某個(gè)內(nèi)容,這樣01H、03H、05H… 會(huì)有很多個(gè)組合都能代表這個(gè)狀態(tài));位容易受干擾被修改,信息出現(xiàn)錯(cuò)誤的幾率大很多。

也不要用00H、FFH等數(shù)據(jù)代表,就像我們面試一群人一樣,第一個(gè)被面試人和最后一個(gè)被面試人容易被記住,00H和FFH亦然,系統(tǒng)默認(rèn)狀態(tài)是00和FF的時(shí)候較多,他們?nèi)菀妆粡?fù)位或置位成這類數(shù)值。推薦以四位的二進(jìn)制碼的某個(gè)中間值為狀態(tài)變量,如1001。

變量數(shù)據(jù)在應(yīng)用之前宜作數(shù)據(jù)類型和數(shù)值范圍的判斷;

數(shù)據(jù)在存儲(chǔ)過程中也容易出現(xiàn)問題,EEPROM、RAM等都有過類似的案例。數(shù)據(jù)出錯(cuò)時(shí)避免不了的,解決的辦法是學(xué)花旗銀行等美國金融企業(yè),之所以在9.11后他們能很快恢復(fù)業(yè)務(wù),基本沒有數(shù)據(jù)方面的損失,原因何在?因?yàn)樗麄冇挟惖厝轂?zāi)數(shù)據(jù)備份系統(tǒng),知里面有兩個(gè)關(guān)鍵詞,異地、備份。我們的信息也同樣,首先選擇存在不同的介質(zhì)中、或相同的介質(zhì)但迥異的存放環(huán)境和位置下,雙重備份的結(jié)局是兩邊不一致的時(shí)候,數(shù)據(jù)被懷疑并拒絕反映執(zhí)行,但嵌入式軟件很多時(shí)候是要靠數(shù)據(jù)來推動(dòng)執(zhí)行機(jī)構(gòu)的,即使發(fā)現(xiàn)數(shù)據(jù)有問題也不允許行政不作為,這種情況下,作為我們也很難辦,2個(gè)不同的數(shù)據(jù),有明顯問題的還好排除,都在有限范圍內(nèi)可如何判定哈?這種時(shí)候沒辦法只好三備份,少數(shù)服從多數(shù)是唯一的選擇了。石頭剪刀布的方式不好用,葛優(yōu)的分歧終端機(jī)也不適用,就只好選擇這種最原始最有效的辦法了,唯一需要注意的是數(shù)據(jù)宜存放于三種不同的備份環(huán)境下,不然豈不成了你家哥倆兒,咋表決都占便宜啊。

以上僅就嵌入式軟件可靠性的關(guān)注方面分了幾大類,進(jìn)行了基本的描述,實(shí)際應(yīng)用中,需要關(guān)注的點(diǎn)還有很多很多,如果是準(zhǔn)備自行制定設(shè)計(jì)規(guī)范的話,以上的思路應(yīng)該也可以給與一些啟迪了。


如何防錯(cuò)


設(shè)備的可靠性涉及多個(gè)方面:穩(wěn)定的硬件、優(yōu)秀的軟件架構(gòu)、嚴(yán)格的測(cè)試以及市場(chǎng)和時(shí)間的檢驗(yàn)等等。這里著重談一下對(duì)嵌入式軟件可靠性設(shè)計(jì)的一些理解,通過一定的技巧和方法提高軟件可靠性。這里所說的嵌入式設(shè)備,是指使用單片機(jī)、ARM7、Cortex-M0,M3之類為核心的測(cè)控或工控系統(tǒng)。

?

嵌入式軟件可靠性設(shè)計(jì)應(yīng)該從防錯(cuò)、判錯(cuò)和容錯(cuò)三方面進(jìn)行考慮. 此外,還需理解自己所使用的編譯器特性。??


此文屬拋磚引玉. ? ??



1.防錯(cuò)


良好的軟件架構(gòu)、清晰的代碼結(jié)構(gòu)、掌握硬件、深入理解C語言是防錯(cuò)的要點(diǎn),這里只談一下C語言。


“人的思維和經(jīng)驗(yàn)積累對(duì)軟件可靠性有很大影響"。C語言詭異且有種種陷阱和缺陷,需要程序員多年歷練才能達(dá)到較為完善的地步。“軟件的質(zhì)量是由程序員的質(zhì)量以及他們相互之間的協(xié)作決定的”。因此,作者認(rèn)為防錯(cuò)的重點(diǎn)是要考慮人的因素。


“深入一門語言編程,不要浮于表面”。軟件的可靠性,與你理解的語言深度密切相關(guān),嵌入式C更是如此。除了語言,作者認(rèn)為嵌入式開發(fā)還必須深入理解編譯器。


本節(jié)將對(duì)C語言的陷阱和缺陷做初步探討。



1.1 處處皆陷阱


最初開始編程時(shí),除了英文標(biāo)點(diǎn)被誤寫成中文標(biāo)點(diǎn)外,可能被大家普遍遇到的是將比較運(yùn)算符==誤寫成賦值運(yùn)算符=,代碼如下所示:

? ? ? ? ? ?

? ?if(x=5) { … }


這里本意是比較變量x是否等于常量5,但是誤將’==’寫成了’=’,if語句恒為真。如果在邏輯判斷表達(dá)式中出現(xiàn)賦值運(yùn)算符,現(xiàn)在的大多數(shù)編譯器會(huì)給出警告信息。并非所有程序員都會(huì)注意到這類警告,因此有經(jīng)驗(yàn)的程序員使用下面的代碼來避免此類錯(cuò)誤:

? ? ? ??

? ? ? if(5==x) { … }


將常量放在變量x的左邊,即使程序員誤將’==’寫成了’=’,編譯器會(huì)產(chǎn)生一個(gè)任誰也不能無視的語法錯(cuò)誤信息:不可給常量賦值!

?

+=與=+、-=與=-也是容易寫混的。復(fù)合賦值運(yùn)算符(+=、*=等等)雖然可以使表達(dá)式更加簡(jiǎn)潔并有可能產(chǎn)生更高效的機(jī)器代碼,但某些復(fù)合賦值運(yùn)算符也會(huì)給程序帶來隱含Bug,如下所示代碼:

? ? ? ? ??

? ? tmp=+1;


該代碼本意是想表達(dá)tmp=tmp+1,但是將復(fù)合賦值運(yùn)算符+=誤寫成=+:將正整數(shù)常量1賦值給變量tmp。編譯器會(huì)欣然接受這類代碼,連警告都不會(huì)產(chǎn)生。


如果你能在調(diào)試階段就發(fā)現(xiàn)這個(gè)Bug,你真應(yīng)該慶祝一下,否則這很可能會(huì)成為一個(gè)重大隱含Bug,且不易被察覺。

?

-=與=-也是同樣道理。與之類似的還有邏輯與&&和位與&、邏輯或||和位或|、邏輯非!和位取反~。此外字母l和數(shù)字1、字母O和數(shù)字0也易混淆,這種情況可借助編譯器來糾正。

? ? ???

很多的軟件BUG自于輸入錯(cuò)誤。在Google上搜索的時(shí)候,有些結(jié)果列表項(xiàng)中帶有一條警告,表明Google認(rèn)為它帶有惡意代碼。如果你在2009年1月31日一大早使用Google搜索的話,你就會(huì)看到,在那天早晨55分鐘的時(shí)間內(nèi),Google的搜索結(jié)果標(biāo)明每個(gè)站點(diǎn)對(duì)你的PC都是有害的。這涉及到整個(gè)Internet上的所有站點(diǎn),包括Google自己的所有站點(diǎn)和服務(wù)。Google的惡意軟件檢測(cè)功能通過在一個(gè)已知攻擊者的列表上查找站點(diǎn),從而識(shí)別出危險(xiǎn)站點(diǎn)。在1月31日早晨,對(duì)這個(gè)列表的更新意外地包含了一條斜杠(“/”)。所有的URL都包含一條斜杠,并且,反惡意軟件功能把這條斜杠理解為所有的URL都是可疑的,因此,它愉快地對(duì)搜索結(jié)果中的每個(gè)站點(diǎn)都添加一條警告。很少見到如此簡(jiǎn)單的一個(gè)輸入錯(cuò)誤帶來的結(jié)果如此奇怪且影響如此廣泛,但程序就是這樣,容不得一絲疏忽。

?

數(shù)組常常也是引起程序不穩(wěn)定的重要因素,C語言數(shù)組的迷惑性與數(shù)組下標(biāo)從0開始密不可分,你可以定義int a[30],但是你絕不可以使用數(shù)組元素a[30],除非你自己明確知道在做什么。

?

switch…case語句可以很方便的實(shí)現(xiàn)多分支結(jié)構(gòu),但要注意在合適的位置添加break關(guān)鍵字。程序員往往容易漏加break從而引起順序執(zhí)行多個(gè)case語句,這也許是C的一個(gè)缺陷之處。對(duì)于switch…case語句,從概率論上說,絕大多數(shù)程序一次只需執(zhí)行一個(gè)匹配的case語句,而每一個(gè)這樣的case語句后都必須跟一個(gè)break。去復(fù)雜化大概率事件,這多少有些不合常情。

?

break關(guān)鍵字用于跳出最近的那層循環(huán)語句或者switch語句,但程序員往往不夠重視這一點(diǎn)。



1990年1月15日,AT&T電話網(wǎng)絡(luò)位于紐約的一臺(tái)交換機(jī)當(dāng)機(jī)并且重啟,引起它鄰近交換機(jī)癱瘓,由此及彼,一個(gè)連著一個(gè),很快,114臺(tái)交換機(jī)每六秒當(dāng)機(jī)重啟一次,六萬人九小時(shí)內(nèi)不能打長途電話。當(dāng)時(shí)的解決方式:工程師重裝了以前的軟件版本。事后的事故調(diào)查發(fā)現(xiàn),這是break關(guān)鍵字誤用造成的?!禖專家編程》提供了一個(gè)簡(jiǎn)化版的問題源碼:


network code()
{
? ? ? switch(line) {
? ? ? ? ? ? ?case ?THING1:
? ? ? ? ? ? ? ? ? ? doit1();
? ? ? ? ? ? ?break;
? ? ? ? ? ? ?case ?THING2:
? ? ? ? ? ? ? ? ? ? if(x==STUFF) {
? ? ? ? ? ? ? ? ? ? ? ? ? ?do_first_stuff();
? ? ? ? ? ? ? ? ? ? ? ? ? ?if(y==OTHER_STUFF)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? ? ? ? ?do_later_stuff();
? ? ? ? ? ? ? ? ? ? } /*代碼的意圖是跳轉(zhuǎn)到這里… …*/
? ? ? ? ? ? ? ? ? ? initialize_modes_pointer();
? ? ? ? ? ? ?break;
? ? ? ? ? ? ?default:
? ? ? ? ? ? ? ? ? ? processing();
? ? ? }/*… …但事實(shí)上跳到了這里。*/
? ? ? use_modes_pointer();/*致使modes_pointer未初始化*/
}

那個(gè)程序員希望從if語句跳出,但他卻忘記了break關(guān)鍵字實(shí)際上跳出最近的那層循環(huán)語句或者switch語句?,F(xiàn)在它跳出了switch語句,執(zhí)行了use_modes_pointer()函數(shù)。但必要的初始化工作并未完成,為將來程序的失敗埋下了伏筆。

?

將一個(gè)整形常量賦值給變量,代碼如下所示:

?????? ?

??????int?a=34, b=034;


變量a和b相等嗎?答案是不相等的。我們知道,16進(jìn)制常量以’0x’為前綴,10進(jìn)制常量不需要前綴,那么8進(jìn)制呢?它與10進(jìn)制和16進(jìn)制表示方法都不相通,它以數(shù)字’0’為前綴,這多少有點(diǎn)奇葩:三種進(jìn)制的表示方法完全不相通。如果8進(jìn)制也像16進(jìn)制那樣以數(shù)字和字母表示前綴的話,或許更有利于減少軟件Bug,畢竟你使用8進(jìn)制的次數(shù)可能都不會(huì)有誤使用的次數(shù)多!下面展示一個(gè)誤用8進(jìn)制的例子,最后一個(gè)數(shù)組元素賦值錯(cuò)誤:


a[0]=106; ? ? ? ? ? ? ?/*十進(jìn)制數(shù)106*/
a[1]=112; ? ? ?/*十進(jìn)制數(shù)112*/
a[2]=052; ? ? ? ? ? ? ?/*實(shí)際為十進(jìn)制數(shù)42,本意為十進(jìn)制52*/


指針的加減運(yùn)算是特殊的。下面的代碼運(yùn)行在32位ARM架構(gòu)上,執(zhí)行之后,a和p的值分別是多少?


 ? ? ? ? ? ? ?int a=1;
? ? ? ? ? ? ?int *p=(int*)0x00001000;
? ? ? ? ? ? ?a=a+1;
? ? ? ? ? ? ?p=p+1;

對(duì)于a的值很容判斷出結(jié)果為2,但是p的結(jié)果卻是0x00001004。指針p加1后,p的值增加了4,這是為什么呢?原因是指針做加減運(yùn)算時(shí)是以指針的數(shù)據(jù)類型為單位。p+1實(shí)際上是p+1*sizeof(int)。不理解這一點(diǎn),在使用指針直接操作數(shù)據(jù)時(shí)極易犯錯(cuò)。比如下面對(duì)連續(xù)RAM初始化零操作代碼:


unsigned int *pRAMaddr; ? ? ? ? ? ? ? ? ? //定義地址指針變量
for(pRAMaddr=StartAddr;pRAMaddr{
? ? ? ? ? *pRAMaddr=0x00000000; ? ?//指定RAM地址清零
}


由于pRAMaddr是一個(gè)指針變量,所以pRAMaddr+=4代碼其實(shí)使pRAMaddr偏移了4*sizeof(int)=16個(gè)字節(jié),所以每執(zhí)行一次for循環(huán),會(huì)使變量pRAMaddr偏移16個(gè)字節(jié)空間,但只有4字節(jié)空間被初始化為零。其它的12字節(jié)數(shù)據(jù)的內(nèi)容,在大多數(shù)架構(gòu)處理器中都會(huì)是隨機(jī)數(shù)。

?

對(duì)于sizeof(),這里強(qiáng)調(diào)兩點(diǎn),第一它是一個(gè)關(guān)鍵字,而不是函數(shù),并且它默認(rèn)返回?zé)o符號(hào)整形數(shù)據(jù)(要記住是無符號(hào));第二,使用sizeof獲取數(shù)組長度時(shí),不要對(duì)指針應(yīng)用sizeof操作符,比如下面的例子:


void ClearRAM(char array[])
{
? ?int i ;
? ?for(i=0;i ? ? ? {
? ? ? ? ? ? ?array[i]=0x00;
? ? ? }
}

int main(void)
{
? ? ? char Fle[20];
? ? ?
? ? ? ClearRAM(Fle); ? ? ? ? ? ? ? ? ? //只能清除數(shù)組Fle中的前四個(gè)元素
}


我們知道,對(duì)于一個(gè)數(shù)組array[20],我們使用代碼sizeof(array)/sizeof(array[0])可以獲得數(shù)組的元素(這里為20),但數(shù)組名和指針往往是容易混淆的,而且有且只有一種情況下是可以當(dāng)做指針的,那就是數(shù)組名作為函數(shù)形參時(shí),數(shù)組名被認(rèn)為是指針。同時(shí),它不能再兼任數(shù)組名。注意只有這種情況下,數(shù)組名才可以當(dāng)做指針,但不幸的是這種情況下容易引發(fā)風(fēng)險(xiǎn)。在ClearRAM函數(shù)內(nèi),作為形參的array[]不再是數(shù)組名了,而成了指針。sizeof(array)相當(dāng)于求指針變量占用的字節(jié)數(shù),在32位系統(tǒng)下,該值為4,sizeof(array)/sizeof(array[0])的運(yùn)算結(jié)果也為4。所以在main函數(shù)中調(diào)用ClearRAM(Fle),也只能清除數(shù)組Fle中的前四個(gè)元素了。

?

增量運(yùn)算符++和減量運(yùn)算符--既可以做前綴也可以做后綴。前綴和后綴的區(qū)別在于值的增加或減少這一動(dòng)作發(fā)生的時(shí)間是不同的。作為前綴是先自加或自減然后做別的運(yùn)算,作為后綴時(shí),是先做運(yùn)算,之后再自加或自減。許多程序員對(duì)此認(rèn)識(shí)不夠,就容易埋下隱患。下面的例子可以很好的解釋前綴和后綴的區(qū)別。


int a=8,b=2,y;
y=a+++--b;


代碼執(zhí)行后,y的值是多少?


這個(gè)例子并非是挖空心思設(shè)計(jì)出來專門讓你絞盡腦汁的C難題(如果你覺得自己對(duì)C細(xì)節(jié)掌握很有信心,做一些C難題檢驗(yàn)一下是個(gè)不錯(cuò)的選擇。那么,《The C Puzzle Book》這本書一定不要錯(cuò)過。),你甚至可以將這個(gè)難懂的語句作為不友好代碼的反面例子。但是它也可以讓你更好的理解C語言。根據(jù)運(yùn)算符優(yōu)先級(jí)以及編譯器識(shí)別字符的貪心法原則,代碼y=a+++--b;可以寫成更明確的形式:


y=(a++)+(--b);


當(dāng)賦值給變量y時(shí),a的值為8,b的值為1,所以變量y的值為9;賦值完成后,變量a自加,a的值變?yōu)?,千萬不要以為y的值為10。這條賦值語句相當(dāng)于下面的兩條語句:


y=a+(--b);
a=a+1;



1.2 玩具般的編譯器語義檢查

?


為了更簡(jiǎn)單的設(shè)計(jì)編譯器,目前幾乎所有編譯器的語義檢查都比較弱小,加之為了獲得更快的執(zhí)行效率,C語言被設(shè)計(jì)的足夠靈活且?guī)缀醪贿M(jìn)行任何運(yùn)行時(shí)檢查,比如數(shù)組越界、指針是否合法、運(yùn)算結(jié)果是否溢出等等。


C語言足夠靈活,對(duì)于一個(gè)數(shù)組a[30],它允許使用像a[-1]這樣的形式來快速獲取數(shù)組首元素所在地址前面的數(shù)據(jù);允許將一個(gè)常數(shù)強(qiáng)制轉(zhuǎn)換為函數(shù)指針,使用代碼(*((void(*)())0))()來調(diào)用位于0地址的函數(shù)。C語言給了程序員足夠的自由,但也由程序員承擔(dān)濫用自由帶來的責(zé)任。下面的兩個(gè)例子都是死循環(huán),如果在不常用分支中出現(xiàn)類似代碼,將會(huì)造成看似莫名其妙的死機(jī)或者重啟。


a.?????unsigned char?i;??????????????????? b. ??unsigned chari;

???????for(i=0;i<256;i++)? {… }?????????????????for(i=10;i>=0;i--) { … }


對(duì)于無符號(hào)char類型,表示的范圍為0~255,所以無符號(hào)char類型變量i永遠(yuǎn)小于256(第一個(gè)for循環(huán)無限執(zhí)行),永遠(yuǎn)大于等于0(第二個(gè)for循環(huán)無線執(zhí)行)。需要說明的是,賦值代碼i=256是被C語言允許的,即使這個(gè)初值已經(jīng)超出了變量i可以表示的范圍。C語言會(huì)千方百計(jì)的為程序員創(chuàng)造出錯(cuò)的機(jī)會(huì),可見一斑。

??????

假如你在if語句后誤加了一個(gè)分號(hào)改變了程序邏輯,編譯器也會(huì)很配合的幫忙掩蓋,甚至連警告都不提示。代碼如下:


 ? ? ? if(a>b); ? ? ? ? ?//這里誤加了一個(gè)分號(hào)
? ? ? a=b; ? ? ? ? ? ? ? //這句代碼一直被執(zhí)行


不但如此,編譯器還會(huì)忽略掉多余的空格符和換行符,就像下面的代碼也不會(huì)給出足夠提示:


 ? ? ? if(n<3)
? ? ? return ? ?//這里少加了一個(gè)分號(hào)
? ? ? logrec.data=x[0];
? ? ? logrec.time=x[1];
? ? ? logrec.code=x[2];


這段代碼的本意是n<3時(shí)程序直接返回,由于程序員的失誤,return少了一個(gè)結(jié)束分號(hào)。編譯器將它翻譯成返回表達(dá)式logrec.data=x[0]的結(jié)果,return后面即使是一個(gè)表達(dá)式也是C語言允許的。這樣當(dāng)n>=3時(shí),表達(dá)式logrec.data=x[0];就不會(huì)被執(zhí)行,給程序埋下了隱患。


可以毫不客氣的說,弱小的編譯器語義檢查在很大程度上縱容了不可靠代碼可以肆無忌憚的存在。

??????

上文曾提到數(shù)組常常是引起程序不穩(wěn)定的重要因素,程序員往往不經(jīng)意間就會(huì)寫數(shù)組越界。一位同事的代碼在硬件上運(yùn)行,一段時(shí)間后就會(huì)發(fā)現(xiàn)LCD顯示屏上的一個(gè)數(shù)字不正常的被改變。經(jīng)過一段時(shí)間的調(diào)試,問題被定位到下面的一段代碼中:? ?


 ? ? ?int SensorData[30];
? ? ? …for(i=30;i>0;i--)
? ? ? {
? ? ? ? ? ? ?SensorData[i]=…;
? ? ? ? ? ? ?…
? ? ? }


這里聲明了擁有30個(gè)元素的數(shù)組,不幸的是for循環(huán)代碼中誤用了本不存在的數(shù)組元素SensorData[30],但C語言卻默許這么使用,并欣然的按照代碼改變了數(shù)組元素SensorData[30]所在位置的值, SensorData[30]所在的位置原本是一個(gè)LCD顯示變量,這正是顯示屏上的那個(gè)值不正常被改變的原因。真慶幸這么輕而易舉的發(fā)現(xiàn)了這個(gè)Bug。


其實(shí)很多編譯器會(huì)對(duì)上述代碼產(chǎn)生一個(gè)警告:賦值超出數(shù)組界限。但并非所有程序員都對(duì)編譯器警告保持足夠敏感,況且,編譯器也并不能檢查出數(shù)組越界的所有情況。舉一個(gè)例子,你在模塊A中定義數(shù)組:

int?SensorData[30];


在模塊B中引用該數(shù)組,但由于你引用代碼并不規(guī)范,這里沒有顯示聲明數(shù)組大小,但編譯器也允許這么做:


extern int?SensorData[];

? ? ? ?如果在模塊B中存在和上面一樣的代碼:


 ? ? ? for(i=30;i>0;i--)
? ? ? {
? ? ? ? ? ? ?SensorData[i]=…;
? ? ? ? ? ? ?…
? ? ? }


這次,編譯器不會(huì)給出警告信息,因?yàn)榫幾g器壓根就不知道數(shù)組的元素個(gè)數(shù)。所以,當(dāng)一個(gè)數(shù)組聲明為具有外部鏈接,它的大小應(yīng)該顯式聲明。


再舉一個(gè)編譯器檢查不出數(shù)組越界的例子。函數(shù)func()的形參是一個(gè)數(shù)組形式,函數(shù)代碼簡(jiǎn)化如下所示:


char * func(char SensorData[30])
{
? ? ? ? ? ? unsignedint i;
? ? ? ? ? ? ?for(i=30;i>0;i--)
? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? SensorData[i]=…;
? ? ? ? ? ? ? ? ? ? …
? ? ? ? ? ? ?}
}


這個(gè)給SensorData[30]賦初值的語句,編譯器也是不給任何警告的。實(shí)際上,編譯器是將數(shù)組名Sensor隱含的轉(zhuǎn)化為指向數(shù)組第一個(gè)元素的指針,函數(shù)體是使用指針的形式來訪問數(shù)組的,它當(dāng)然也不會(huì)知道數(shù)組元素的個(gè)數(shù)了。造成這種局面的原因之一是C編譯器的作者們認(rèn)為指針代替數(shù)組可以提高程序效率,而且,還可以簡(jiǎn)化編譯器的復(fù)雜度。


指針和數(shù)組是容易給程序造成混亂的,我們有必要仔細(xì)的區(qū)分它們的不同。其實(shí)換一個(gè)角度想想,它們也是容易區(qū)分的:可以將數(shù)組名等同于指針的情況有且只有一處,就是上面例子提到的數(shù)組作為函數(shù)形參時(shí)。其它時(shí)候,數(shù)組名是數(shù)組名,指針是指針。

下面的例子編譯器同樣檢查不出數(shù)組越界。


我們常常用數(shù)組來緩存通訊中的一幀數(shù)據(jù)。在通訊中斷中將接收的數(shù)據(jù)保存到數(shù)組中,直到一幀數(shù)據(jù)完全接收后再進(jìn)行處理。即使定義的數(shù)組長度足夠長,接收數(shù)據(jù)的過程中也可能發(fā)生數(shù)組越界,特別是干擾嚴(yán)重時(shí)。這是由于外界的干擾破壞了數(shù)據(jù)幀的某些位,對(duì)一幀的數(shù)據(jù)長度判斷錯(cuò)誤,接收的數(shù)據(jù)超出數(shù)組范圍,多余的數(shù)據(jù)改寫與數(shù)組相鄰的變量,造成系統(tǒng)崩潰。由于中斷事件的異步性,這類數(shù)組越界編譯器無法檢查到。


如果局部數(shù)組越界,可能引發(fā)ARM架構(gòu)硬件異常。同事的一個(gè)設(shè)備用于接收無線傳感器的數(shù)據(jù),一次軟件升級(jí)后,發(fā)現(xiàn)接收設(shè)備工作一段時(shí)間后會(huì)死機(jī)。調(diào)試表明ARM7處理器發(fā)生了硬件異常,異常處理代碼是一段死循環(huán)(死機(jī)的直接原因)。接收設(shè)備有一個(gè)硬件模塊用于接收無線傳感器的整包數(shù)據(jù)并存在自己的硬件緩沖區(qū)中,當(dāng)一幀數(shù)據(jù)接收完成后,使用外部中斷通知設(shè)備取數(shù)據(jù),外部中斷服務(wù)程序精簡(jiǎn)后如下所示:


 ? ? ? __irq ExintHandler(void)
? ? ? {
? ? ? ? ? ? ?unsignedchar DataBuf[50];
? ? ? ? ? ? ?GetData(DataBug); ? ? ? ?//從硬件緩沖區(qū)取一幀數(shù)據(jù)
? ? ? ? ? ? ?…
? ? ? }


由于存在多個(gè)無線傳感器近乎同時(shí)發(fā)送數(shù)據(jù)的可能加之GetData()函數(shù)保護(hù)力度不夠,數(shù)組DataBuf在取數(shù)據(jù)過程中發(fā)生越界。由于數(shù)組DataBuf為局部變量,被分配在堆棧中,同在此堆棧中的還有中斷發(fā)生時(shí)的運(yùn)行環(huán)境以及中斷返回地址。溢出的數(shù)據(jù)將這些數(shù)據(jù)破壞掉,中斷返回時(shí)PC指針可能變成一個(gè)不合法值,硬件異常由此產(chǎn)生。


如果我們精心設(shè)計(jì)溢出部分的數(shù)據(jù),化數(shù)據(jù)為指令,就可以利用數(shù)組越界來修改PC指針的值,使之指向我們希望執(zhí)行的代碼。1988年,第一個(gè)網(wǎng)絡(luò)蠕蟲在一天之內(nèi)感染了2000到6000臺(tái)計(jì)算機(jī),這個(gè)蠕蟲程序利用的正是一個(gè)標(biāo)準(zhǔn)輸入庫函數(shù)的數(shù)組越界Bug。起因是一個(gè)標(biāo)準(zhǔn)輸入輸出庫函數(shù)gets(),原來設(shè)計(jì)為從數(shù)據(jù)流中獲取一段文本,遺憾的是,gets()函數(shù)沒有規(guī)定輸入文本的長度。gets()函數(shù)內(nèi)部定義了一個(gè)500字節(jié)的數(shù)組,攻擊者發(fā)送了大于500字節(jié)的數(shù)據(jù),利用溢出的數(shù)據(jù)修改了堆棧中的PC指針,從而獲取了系統(tǒng)權(quán)限。

??????

一個(gè)程序模塊通常由兩個(gè)文件組成,源文件和頭文件。如果你在源文件定義變量:

unsigned int?a;


并在頭文件中聲明該變量:extern unsigned long?a;


編譯器會(huì)提示一個(gè)語法錯(cuò)誤:變量’a’聲明類型不一致。但如果你在源文件定義變量:

volatile unsigned int?a,


在頭文件中聲明變量:extern unsigned int?a;?????/*缺少volatile限定符*/

? ? ? ?

編譯器卻不會(huì)給出錯(cuò)誤信息(有些編譯器僅給出一條警告)。這里volatile屬于類型限定符,另一個(gè)常見的類型限定符是const關(guān)鍵字。限定符volatile在嵌入式軟件中至關(guān)重要,用來告訴編譯器不要優(yōu)化它修飾的變量。這里舉一個(gè)刻意構(gòu)造出的例子,因?yàn)楝F(xiàn)實(shí)中的volatile使用Bug大都隱含且難以理解。

???????

在模塊A的源文件中,定義變量:


volatile unsigned int?TimerCount=0;


該變量用來在一個(gè)定時(shí)器服務(wù)程序中進(jìn)行軟件計(jì)時(shí):

?????? ?????? TimerCount++;???????????? ??????????????//讀取IO端口1的值

?????? 在模塊A的頭文件中,聲明變量:

extern unsigned int?TimerCount;???//這里漏掉了類型限定符volatile

?????? 在模塊B中,要使用TimerCount變量進(jìn)行精確的軟件延時(shí):


 ? ? ? ? ? ? ?#include “...A.h” ? //首先包含模塊A的頭文件
? ? ? ? ? ? ?…
? ? ? ? ? ? ?TimerCount=0;
? ? ? ? ? ? ?while(TimerCount>=TIMER_VALUE); ? ? ?//延時(shí)一段時(shí)間
? ? ? ? ? ? ?…


實(shí)際上,這是一個(gè)死循環(huán)。由于模塊A頭文件中聲明變量TimerCount時(shí)漏掉了volatile限定符,在模塊B中,變量TimerCount是被當(dāng)作unsigned int類型變量。由于寄存器速度遠(yuǎn)快于RAM,編譯器在使用非volatile限定變量時(shí)是先將變量從RAM中拷貝到寄存器中,如果同一個(gè)代碼塊再次用到該變量,就不再從RAM中拷貝數(shù)據(jù)而是直接使用之前寄存器備份值。代碼while(TimerCount>=TIMER_VALUE)中,變量TimerCount僅第一次執(zhí)行時(shí)被使用,之后都是使用的寄存器備份值,而這個(gè)寄存器值一直為0,所以程序無限循環(huán)。下面的流程圖說明了程序使用限定符volatile和不使用volatile的執(zhí)行過程。



? ? ? ?

ARM架構(gòu)下的編譯器會(huì)頻繁的使用堆棧,堆棧用于存儲(chǔ)函數(shù)的返回值、AAPCS規(guī)定的必須保護(hù)的寄存器以及局部變量,包括局部數(shù)組、結(jié)構(gòu)體、聯(lián)合體和C++的類。從堆棧中分配的局部變量的初值是不確定的,因此需要運(yùn)行時(shí)顯式初始化該變量。一旦離開局部變量的作用域,這個(gè)變量立即被釋放,其它代碼也就可以使用它,因此堆棧中的一個(gè)內(nèi)存位置可能對(duì)應(yīng)整個(gè)程序的多個(gè)變量。

???????

局部變量必須顯式初始化,除非你確定知道你要做什么。下面的代碼得到的溫度值跟預(yù)期會(huì)有很大差別,因?yàn)樵谑褂镁植孔兞縮um時(shí),并不能保證它的初值為0。編譯器會(huì)在第一次運(yùn)行時(shí)清零堆棧區(qū)域,這加重了此類Bug的隱蔽性。


 ? ? ? unsigned intGetTempValue(void)
? ? ? {
? ? ? ? ? ? ?unsigned int sum; ? ? ? ? ? ? ? ? ? ? ? //定義局部變量,保存總值
? ? ? ? ? ? ?for(i=0;i<10;i++)
? ? ? ? ? ? ?{
? ? ? ? ? ? ? ? ? ? sum+=CollectTemp(); ? ? ? ? ? ? ? //函數(shù)CollectTemp可以得到當(dāng)前的溫度值
? ? ? }
? ? ? return (sum/10);
? ? ? }


由于一旦程序離開局部變量的作用域即被釋放,所以下面代碼返回指向局部變量的指針是沒有實(shí)際意義的,該指針指向的區(qū)域可能會(huì)被其它程序使用,其值會(huì)被改變。


 ? ? ? char * GetData(void)
? ? ? {
? ? ? ? ? ? ?char buffer[100]; ? ? ? ? ? ? ? ? //局部數(shù)組
? ? ? ? ? ? ?…
? ? ? ? ? ? ?return buffer;
? ? ? }


讓人欣慰的是,現(xiàn)在越來越多的編譯器意識(shí)到了語義檢查的重要性,編譯器的語義檢查也越來越強(qiáng)大,比如著名的Keil MDK編譯器在其 V4.47或以上版本中增加了動(dòng)態(tài)語法檢查并加強(qiáng)了語義檢查,可以友好的提示更多警告信息。




1.3 不合理的優(yōu)先級(jí)


C語言有32個(gè)關(guān)鍵字,卻有34個(gè)運(yùn)算符。要記住所有運(yùn)算符的優(yōu)先級(jí)是困難的。不合理的#define會(huì)加重優(yōu)先級(jí)問題,讓問題變得更加隱蔽。


 ? ? ? #define READSDA ? ? ? IO0PIN&(1<<11) ? ? ? ? ? ?//定義宏,讀IO口p0.11的端口狀態(tài)
? ? ? ? ? ? ?//判斷端口p0.11是否為高電平
? ? ? if(READSDA==(1<<11)) ?
? ? ? { ?
? ? ? ? ? ? ?…
? ? ? }


?????? 編譯器在編譯后將宏帶入,原if語句變?yōu)?


 ? ? ? if(IO0PIN&(1<<11) ==(1<<11))
? ? ? {
? ? ? ? ? ? ? ? ? ? …
? ? ? }



運(yùn)算符'=='的優(yōu)先級(jí)是大于'&'的,代碼IO0PIN&(1<<11)?==(1<<11))等效為IO0PIN&0x00000001:判斷端口P0.0是否為高電平,這與原意相差甚遠(yuǎn)。


為了制造更多的軟件Bug,C語言的運(yùn)算符當(dāng)然不會(huì)只止步于數(shù)目繁多。在此基礎(chǔ)上,按照常規(guī)方式使用時(shí),可能引起誤會(huì)的運(yùn)算符更是比比皆是!如下表所示:


常被誤會(huì)的

優(yōu)先級(jí)

表達(dá)式

常被誤認(rèn)為:

其實(shí)是:

取值運(yùn)算符*與自增運(yùn)算符++優(yōu)先級(jí)相同,但它們是自右向左結(jié)合

*p++

(*p)++

*(p++)

成員選擇運(yùn)算符.高于取值運(yùn)算符*

*p.f

(*p).f

*(p.f)

數(shù)組下標(biāo)運(yùn)算符[]優(yōu)先級(jí)高于取值運(yùn)算符*

int *ap[]

int (*ap)[]

ap為數(shù)組指針

int *(ap[])

ap為指針數(shù)組

函數(shù)()優(yōu)先級(jí)高于取值運(yùn)算符*

int * fp()

int (*fp)()

fp為函數(shù)指針

int * (fp())

fp為函數(shù),返回指針

等于==和不等于!=運(yùn)算符優(yōu)先級(jí)高于位操作運(yùn)算符&、^ 和 |

val & mask != 0

(val & mask)!= 0

val &(mask != 0)

等于==和不等于!=運(yùn)算符高于賦值運(yùn)算符=

c=getchar()!=EOF

(c=getchar())!=EOF

c=(getchar()!=EOF)

算數(shù)運(yùn)算符+和-優(yōu)先級(jí)高于移位運(yùn)算符<<和>>

msb<<4+lsb

(msb<<4)+lsb

msb<<(4+lsb)


1.4 隱式轉(zhuǎn)換和強(qiáng)制轉(zhuǎn)換


這又是C語言的一大詭異之處,它造成的危害程度與數(shù)組和指針有的一拼。語句或表達(dá)式通常應(yīng)該只使用一種類型的變量和常量。然而,如果你混合使用類型,C使用一個(gè)規(guī)則集合來自動(dòng)完成類型轉(zhuǎn)換。這可能很方便,但也很危險(xiǎn)。


a.當(dāng)出現(xiàn)在表達(dá)式里時(shí),有符號(hào)和無符號(hào)的char和short類型都將自動(dòng)被轉(zhuǎn)換為int類型,在需要的情況下,將自動(dòng)被轉(zhuǎn)換為unsigned int(在short和int具有相同大小時(shí))。這稱為類型提升。提升在算數(shù)運(yùn)算中通常不會(huì)有什么大的壞處,但如果位運(yùn)算符 ~ 和 << 應(yīng)用在基本類型為unsigned char或unsigned short 的操作數(shù),結(jié)果應(yīng)該立即強(qiáng)制轉(zhuǎn)換為unsigned char或者unsigned short類型(取決于操作時(shí)使用的類型)。


 ? ? ? uint8_t ?port =0x5aU;
? ? ? uint8_t ?result_8;
? ? ? result_8= (~port) >> 4;


假如我們不了解表達(dá)式里的類型提升,認(rèn)為在運(yùn)算過程中變量port一直是unsigned char類型的。我們來看一下運(yùn)算過程:~port結(jié)果為0xa5,0xa5>>4結(jié)果為0x0a,這是我們期望的值。但實(shí)際上,result_8的結(jié)果卻是0xfa!在ARM結(jié)構(gòu)下,int類型為32位。變量port在運(yùn)算前被提升為int類型:~port結(jié)果為0xffffffa5,0xa5>>4結(jié)果為0x0ffffffa,賦值給變量result_8,發(fā)生類型截?cái)啵ㄟ@也是隱式的!),result_8=0xfa。經(jīng)過這么詭異的隱式轉(zhuǎn)換,結(jié)果跟我們期望的值,已經(jīng)大相徑庭!正確的表達(dá)式語句應(yīng)該為:

?????? result_8=(unsigned char) (~port) >> 4;?????????????/*強(qiáng)制轉(zhuǎn)換*/

b.在包含兩種數(shù)據(jù)類型的任何運(yùn)算里,兩個(gè)值都會(huì)被轉(zhuǎn)換成兩種類型里較高的級(jí)別。類型級(jí)別從高到低的順序是long double、double、float、unsigned long long、long long、unsigned long、long、unsigned int、int。這種類型提升通常都是件好事,但往往有很多程序員不能真正理解這句話,從而做一些想當(dāng)然的事情,比如下面的例子,int類型表示16位。


 ? ? ? uint16_t ?u16a = 40000; ? ? ? ? ? ?/* 16位無符號(hào)變量*/
? ? ? uint16_t ?u16b= 30000; ? ? ? ? ?/*16位無符號(hào)變量*/
? ? ? uint32_t ?u32x; ? ? ? ? ? ? ? ? ? ? ? ?/*32位無符號(hào)變量 */
? ? ? uint32_t ?u32y;
? ? ? u32x = u16a +u16b; ? ? ? ? ? ? ? ?/* u32x = 70000還是4464 ? */
? ? ? u32y =(uint32_t)(u16a + u16b); ? /* u32y = 70000 還是4464 ? */


u32x和u32y的結(jié)果都是4464(70000%65536)!不要認(rèn)為表達(dá)式中有一個(gè)高類別uint32_t類型變量,編譯器都會(huì)幫你把所有其他低類別都提升到uint32_t類型。正確的書寫方式:


 ? ? ? ? ? ? ? ? ?  u32x = (uint32_t)u16a +(uint32_t)u16b;或者:
? ? ? ? ? ? ? ? ? ?u32x = (uint32_t)u16a + u16b;


后一種寫法在本表達(dá)式中是正確的,但是在其它表達(dá)式中不一定正確,比如:


 ? ? ? ? ? ? ? ? ? uint16_t u16a,u16b,u16c;
? ? ? ? ? ? ? ? ? uint32_t ?u32x;
? ? ? ? ? ? ? ? ? u32x= u16a + u16b + (uint32_t)u16c;/*錯(cuò)誤寫法,u16a+ u16b仍可能溢出*/


c.在賦值語句里,計(jì)算的最后結(jié)果被轉(zhuǎn)換成將要被賦予值得那個(gè)變量的類型。這一過程可能導(dǎo)致類型提升也可能導(dǎo)致類型降級(jí)。降級(jí)可能會(huì)導(dǎo)致問題。比如將運(yùn)算結(jié)果為321的值賦值給8位char類型變量。程序必須對(duì)運(yùn)算時(shí)的數(shù)據(jù)溢出做合理的處理。


很多其他語言,像Pascal語言(好笑的是C語言設(shè)計(jì)者之一曾撰文狠狠批評(píng)過Pascal語言),都不允許混合使用類型,但C語言不會(huì)限制你的自由,即便這經(jīng)常引起B(yǎng)ug。


d.當(dāng)作為函數(shù)的參數(shù)被傳遞時(shí),char和short會(huì)被轉(zhuǎn)換為int,float會(huì)被轉(zhuǎn)換為double。


e.C語言支持強(qiáng)制類型轉(zhuǎn)換,如果你必須要進(jìn)行強(qiáng)制類型轉(zhuǎn)換時(shí),要確保你對(duì)類型轉(zhuǎn)換有足夠了解:


  • 并非所有強(qiáng)制類型轉(zhuǎn)換都是由風(fēng)險(xiǎn)的,把一個(gè)整數(shù)值轉(zhuǎn)換為一種具有相同符號(hào)的更寬類型時(shí),是絕對(duì)安全的。

  • 精度高的類型強(qiáng)制轉(zhuǎn)換為精度低的類型時(shí),通過丟棄適當(dāng)數(shù)量的最高有效位來獲取結(jié)果,也就是說會(huì)發(fā)生數(shù)據(jù)截?cái)?,并且可能改變?shù)據(jù)的符號(hào)位。

  • ?精度低的類型強(qiáng)制轉(zhuǎn)換為精度高的類型時(shí),如果兩種類型具有相同的符號(hào),那么沒什么問題;需要注意的是負(fù)的有符號(hào)精度低類型強(qiáng)制轉(zhuǎn)換為無符號(hào)精度高類型時(shí),會(huì)不直觀的執(zhí)行符號(hào)擴(kuò)展,例如:


unsigned int bob;
signed char fred = -1;

bob=(unsigned int )fred; ? ? ? ? ? ? ?/*發(fā)生符號(hào)擴(kuò)展,此時(shí)bob為0xFFFFFFFF*/


一些編程建議:

  • ?深入理解嵌入式C語言以及編譯器

  • ?細(xì)致、謹(jǐn)慎的編程

  • 使用好的風(fēng)格和合理的設(shè)計(jì)

  • 不要倉促編寫代碼,寫每一行的代碼時(shí)都要三思而后行:可能會(huì)出現(xiàn)什么樣的錯(cuò)誤?是否考慮了所有的邏輯分支?

  • 打開編譯器所有警告開關(guān)

  • 使用靜態(tài)分析工具分析代碼

  • 安全的讀寫數(shù)據(jù)(檢查所有數(shù)組邊界…)

  • 檢查指針的合法性

  • 檢查函數(shù)入口參數(shù)合法性

  • 檢查所有返回值

  • 在聲明變量位置初始化所有變量

  • 合理的使用括號(hào)

  • 謹(jǐn)慎的進(jìn)行強(qiáng)制轉(zhuǎn)換

  • 使用好的診斷信息日志和工具


-END-


|?整理文章為傳播相關(guān)技術(shù),版權(quán)歸原作者所有?|

|?如有侵權(quán),請(qǐng)聯(lián)系刪除?|


【1】C語言進(jìn)階技術(shù):同事這些操作把我驚呆了!

【2】初學(xué)者:如何學(xué)好C語言?

【3】推薦給初學(xué)者的箴言:如何學(xué)好C語言

【4】C語言與C++40 年的愛恨情仇!

【5】C語言中的短路現(xiàn)象



免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

嵌入式ARM

掃描二維碼,關(guān)注更多精彩內(nèi)容

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉