加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專(zhuān)業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

嵌入式工程師必備的 8 個(gè)C語(yǔ)言技巧

2023/12/13
3169
閱讀需 13 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

作為一名玩 MCU工程師,或許我們鮮有遇到純上層軟件的開(kāi)發(fā),也絕不可能完全的做一些硬件電路設(shè)計(jì),我們最常見(jiàn)的工作模式是:左手夾著煙頭,右手拿著烙鐵,雙手架在鍵盤(pán)上敲著代碼。

為了做一個(gè)好的設(shè)計(jì),本身在軟硬件的配合上就需要克服無(wú)數(shù)的困難和障礙,任何一名 MCU 愛(ài)好者都不希望遇到一些因?yàn)檎Z(yǔ)言和工具而產(chǎn)生的困擾,我們?cè)?MCU 這種資源受限的平臺(tái)上進(jìn)行 C語(yǔ)言的開(kāi)發(fā)雖然被軟件界看起來(lái)不怎么高大上,但是 MCU 的開(kāi)發(fā)目前 C 語(yǔ)言還是主流,為了更好的操控和調(diào)試我們的硬件,我們還是需要竭力的避免一些 C語(yǔ)言編程的陷阱,避免被一些高大上的變成語(yǔ)言或者架構(gòu)干擾產(chǎn)品整體的進(jìn)度和可靠性。

這里就介紹幾個(gè) C語(yǔ)言中避坑的技巧

第1坑:不要使用“GOTO”語(yǔ)句

GOTO 語(yǔ)句最早源于匯編語(yǔ)言的跳轉(zhuǎn),在很多年前,計(jì)算機(jī)的變成還處于起步階段,C語(yǔ)言開(kāi)始也是尋著匯編的思路來(lái)設(shè)計(jì)的,因此就遺留下了這么一個(gè) GOTO 語(yǔ)句,允許程序員自由的在代碼間翱翔。使用GOTO語(yǔ)句的例子

#include <stdio.h>
int main() {    int i = 0;
    // 使用goto語(yǔ)句的簡(jiǎn)單示例    goto start;
loop:    printf("Inside loop: %dn", i);    i++;
start:    if (i < 5)        goto loop;
    printf("Loop finished.n");
    return 0;}

這種 goto 語(yǔ)句用起來(lái)簡(jiǎn)單,但是整體程序如果來(lái)回跳轉(zhuǎn),讀起來(lái)會(huì)非常的困難,非常繞,并且 GOTO 語(yǔ)句還存在以下問(wèn)題:

可讀性差:使用goto語(yǔ)句的代碼通常會(huì)變得難以理解,因?yàn)樗试S在程序中跳轉(zhuǎn)到不同的標(biāo)簽位置。這使得代碼流程變得不清晰,增加了理解代碼的難度。

難以維護(hù):當(dāng)代碼包含大量goto語(yǔ)句時(shí),很容易導(dǎo)致代碼的維護(hù)困難。修改代碼或添加新功能時(shí),必須仔細(xì)考慮goto語(yǔ)句的影響,以防止引入錯(cuò)誤。

錯(cuò)誤的使用可能導(dǎo)致問(wèn)題:如果不小心使用了錯(cuò)誤的標(biāo)簽,或者在不當(dāng)?shù)奈恢檬褂?code>goto,可能導(dǎo)致程序的不正確行為。這種錯(cuò)誤可能難以追蹤和修復(fù)。

不利于結(jié)構(gòu)化編程:使用goto語(yǔ)句可能違背結(jié)構(gòu)化編程的原則,使得代碼難以按照清晰的結(jié)構(gòu)組織。結(jié)構(gòu)化編程強(qiáng)調(diào)使用順序結(jié)構(gòu)、選擇結(jié)構(gòu)和循環(huán)結(jié)構(gòu)來(lái)構(gòu)建清晰、可讀、可維護(hù)的代碼。

不利于調(diào)試:調(diào)試時(shí),跳轉(zhuǎn)語(yǔ)句會(huì)使程序的執(zhí)行路徑變得復(fù)雜,增加了調(diào)試的難度。代碼中的跳轉(zhuǎn)可能使得代碼不易于單步調(diào)試,阻礙了查找和修復(fù)錯(cuò)誤的過(guò)程。

第2坑:使用完整的條件語(yǔ)句

在使用判斷語(yǔ)句的時(shí)候,我們尤其要注意判斷條件的完整性,我們?cè)S多工程師都熟悉簡(jiǎn)單的if else 語(yǔ)句,然而有一些工程師卻沒(méi)有注意到,不同的寫(xiě)法可能會(huì)浪費(fèi)一些處理器的時(shí)間。比如:

if(value == 1U){
}
if(value == 0U){
}
if(value == 1U){
}else {
}

在第一種寫(xiě)法中,處理器會(huì)去判斷兩次,然后根據(jù)判斷結(jié)果進(jìn)行分支運(yùn)行,但是如果我們寫(xiě)成第二種寫(xiě)法,處理器只需要判斷一次就可以了。尤其是這種判斷在一個(gè)大循環(huán)內(nèi)部,這將浪費(fèi)我們很多處理器時(shí)間。

另外為了代碼具備更清晰的可讀性,我們應(yīng)該讓 if else 成對(duì)出現(xiàn),并且都是用{}把程序分割開(kāi)來(lái),這樣也避免我們?cè)谡{(diào)試的時(shí)候復(fù)制粘貼出現(xiàn)一些錯(cuò)誤,從而影響我們調(diào)試和解決問(wèn)題的進(jìn)度。

#include <stdio.h>
int main() {    int choice;
    // 提示用戶輸入數(shù)字    printf("Enter a number (1-3): ");    scanf("%d", &choice);
    // 使用 switch 語(yǔ)句根據(jù)用戶輸入執(zhí)行不同的操作    switch (choice) {        case 1:            printf("You chose option 1.n");            // 執(zhí)行操作1的代碼            break;
        case 2:            printf("You chose option 2.n");            // 執(zhí)行操作2的代碼            break;
        case 3:            printf("You chose option 3.n");            // 執(zhí)行操作3的代碼            break;
        default:            printf("Invalid choice. Please enter a number between 1 and 3.n");            // 處理無(wú)效選擇的代碼            break;    }
    return 0;}

如果判斷分支比較多,一定是用 swich case 語(yǔ)句來(lái)代替 if else。道理是相同的,一定要完整且用{}將程序段分隔好。同時(shí)要注意,如果我們對(duì)分支的命中率有一定的前瞻性,那么我們最好把命中率比較高的分支放在前面。

對(duì)于 case 比較多的情況,有些編譯器會(huì)主動(dòng)優(yōu)化,這時(shí)候就不必考慮命中率的問(wèn)題了。

第3坑:使用FOR(;;)還是 While(1)?

MCU 的開(kāi)發(fā)過(guò)程中,我們絕大部分情況下還是在使用前后臺(tái)系統(tǒng),當(dāng)然即便我們跑了一些實(shí)時(shí)性的操作系統(tǒng),也避免不了使用一些無(wú)限循環(huán)的處理。

那么處理無(wú)限循環(huán)的語(yǔ)句目前有兩種寫(xiě)法,我??吹揭恍┏跫?jí)工程師會(huì)使用 while(1),而在一些操作系統(tǒng)源碼中看到的更多的是 for(;;)。

如果在 C99 的版本下,我們使用 for 來(lái)寫(xiě)循環(huán)看起來(lái)更緊湊。

// while 循環(huán)的初始化int i = 0;while (i < 5) {    // ...    i++;}
// for 循環(huán)的初始化for (int i = 0; i < 5; i++) {    // ...}

另外,我十幾年前在賽普拉斯的單片機(jī)上開(kāi)發(fā),因?yàn)?flash 空間很小,需要極致優(yōu)化代碼來(lái)進(jìn)行空間壓縮,這里我選擇了 for 循環(huán)的寫(xiě)法可以讓空間多出一個(gè)字節(jié)來(lái),不過(guò)現(xiàn)在的很多編譯器都已經(jīng)更新了很多年了,至少在主流的 arm 平臺(tái)上他們的匯編代碼都是一樣的了。

第4坑:盡可能不用嵌入?yún)R編語(yǔ)言

微處理器的自然語(yǔ)言為匯編語(yǔ)言指令。為低級(jí)別機(jī)器語(yǔ)言編程可能會(huì)為處理器提供更高效的代碼。然而,人類(lèi)并不是天生就會(huì)這種語(yǔ)言,并且經(jīng)驗(yàn)表明,編寫(xiě)匯編語(yǔ)言會(huì)造成誤解。誤解會(huì)導(dǎo)致維護(hù)不當(dāng),更甚者,可能會(huì)使系統(tǒng)到處是bug,一般建議避免使用匯編語(yǔ)言。

我第一份工作是在地鐵的廣播系統(tǒng),聽(tīng)說(shuō)當(dāng)時(shí)北京地鐵一號(hào)線數(shù)字化改造就是用來(lái)單片機(jī)控制系統(tǒng),當(dāng)時(shí)的老工程師在天津,使用匯編寫(xiě)好的幾頁(yè)紙的匯編代碼,要拿到北京進(jìn)行編譯,并且?guī)缀跏且淮涡跃幾g通過(guò)。

這或許聽(tīng)起來(lái)很神話,但在我們現(xiàn)在在線調(diào)試工具如此豐富的今天,我們完全沒(méi)必要去鍛煉這種技能了。

實(shí)際上,現(xiàn)在大多數(shù)編譯器都能編譯出非常高效的代碼。采用C語(yǔ)言或C++語(yǔ)言等高級(jí)語(yǔ)言的開(kāi)發(fā),能獲得更有序的結(jié)構(gòu),便于理解和維護(hù),使代碼的整體效果更好。

第5坑:寫(xiě)千層餅式代碼而非方便面式代碼

在 MCU 開(kāi)發(fā)的初級(jí)階段,我們往往都是如初生牛犢般生猛,不管三七二一,上來(lái)就是一把梭,一氣呵成的把代碼寫(xiě)完,然后就會(huì)各種抱怨需求變更啦,增加個(gè)小功能麻煩了,等等。

因此,為了避免這些麻煩,我們需要養(yǎng)成架構(gòu)的思維,先按照千層餅式的代碼進(jìn)行規(guī)劃,然后一個(gè)模塊一個(gè)模塊的實(shí)現(xiàn),前面慢一點(diǎn),后面會(huì)走得更遠(yuǎn)。

而如果寫(xiě)的像泡面一樣,形同亂麻會(huì)很容易出現(xiàn)亂碼。糾纏到最后,你連重構(gòu)的信心都沒(méi)有了。

第6坑:要有模塊化思維

MCU 的開(kāi)發(fā)往往面對(duì)很多不同的平臺(tái),有早期的 8051 的,也有現(xiàn)在如火如荼的 ARM Cortex 系列的,但是不管哪種平臺(tái),我們本質(zhì)上都是去操作他們的一些外設(shè),那么我們針對(duì)外設(shè)的上層就會(huì)有很多可以抽象成模塊的代碼,比如串口的發(fā)送接受 FIFO。

再比如我們做一些數(shù)字信號(hào)處理時(shí)的一些算法,求最大最小值,一階低通濾算法等等

我們都可以把這些小型算法抽象到一個(gè)模塊中,以便于在各種不同的平臺(tái)和項(xiàng)目中直接使用。

C語(yǔ)言編程使工程師能夠?qū)⒋a分成獨(dú)立的功能模塊,這簡(jiǎn)化了代碼導(dǎo)航,同時(shí)還能夠使工程師使用封裝等面向?qū)ο蠹夹g(shù)。代碼可以被組織成邏輯模塊,這很有意義。雖然可能要先花點(diǎn)時(shí)間(幾分鐘),但從長(zhǎng)遠(yuǎn)來(lái)看,這將能省掉很多漫長(zhǎng)之夜,和很多調(diào)試之苦!

第7坑:一定要給自己定一套變量命名方式

打江山容易,守江山難,寫(xiě)代碼時(shí)候如果一把梭爽了,后期維護(hù)代碼就會(huì)很痛苦,很多時(shí)候我們真的連自己的注釋都看不懂,因此讓代碼本身就透著意義是很重要的一種技能。

比如我們對(duì)于全局變量和局部變量的命名前面分別增加 g 和 m 開(kāi)頭。

unsinged char g_bValue = 0;
int main(){    short m_wCnt = 0;}

比如在定義變量的時(shí)候根據(jù)變量的類(lèi)型在變量前面增加標(biāo)識(shí):b(字節(jié)),w(字),dw(雙字)

另外,對(duì)于變量的命名還需要注意其本身的意義,我們可以使用完整的英文來(lái)進(jìn)行命名,如果在一個(gè)團(tuán)隊(duì)里面,大家可以約定有效,也可以使用一些簡(jiǎn)短的自創(chuàng)性的命名,當(dāng)然前提是大家要有一個(gè)團(tuán)隊(duì)命名手冊(cè)。

int Freq      //Frequencyint Btn       //Buttonint MotorSta   //MotorStateint Spd       //  Speed

第8坑:少用#pragma語(yǔ)句

C語(yǔ)言中有一種特殊的#pragma語(yǔ)句。這些語(yǔ)句通常處理非標(biāo)準(zhǔn)的句法和特性,應(yīng)盡可能避免使用這種語(yǔ)句,因?yàn)樗鼈兪欠菢?biāo)準(zhǔn)的,不能從一個(gè)處理器移植到另一個(gè)處理器。有些編譯器可能要求用這類(lèi)語(yǔ)句完成某項(xiàng)任務(wù),例如定義一個(gè)中斷服務(wù)程序,這時(shí)候我們應(yīng)該將中斷服務(wù)函數(shù)單獨(dú)寫(xiě)出來(lái),再讓編譯器要求的寫(xiě)法的函數(shù)去調(diào)用,把我們的程序和編譯器特性需求給解耦合。

// 使用 typedef 定義關(guān)鍵字typedef unsigned char U8;typedef unsigned short U16;typedef unsigned int U32;

另外,我們可以在自己的代碼中自定義一些便于移植的數(shù)據(jù)類(lèi)型,這樣以后移植自己的代碼的時(shí)候只需要#include 我們自己的配置文件就可以了。

推薦器件

更多器件
器件型號(hào) 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊(cè) ECAD模型 風(fēng)險(xiǎn)等級(jí) 參考價(jià)格 更多信息
LTC6990CDCB#TRPBF 1 Linear Technology LTC6990 - TimerBlox: Voltage Controlled Silicon Oscillator; Package: DFN; Pins: 6; Temperature Range: 0&deg;C to 70&deg;C
暫無(wú)數(shù)據(jù) 查看
MM74HC541WMX 1 onsemi Octal 3-STATE Buffers, 1000-REEL

ECAD模型

下載ECAD模型
$1.03 查看
NC7SZ125L6X 1 onsemi TinyLogic UHS Buffer with 3-STATE Output, 5000-REEL

ECAD模型

下載ECAD模型
$0.76 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

多年硬件從業(yè)經(jīng)驗(yàn),專(zhuān)注分享從研發(fā)到供應(yīng)鏈,再到精益制造過(guò)程中的經(jīng)驗(yàn)和感悟!