天天看點

寫碼如寫詩!嵌入式C代碼規範有多重要?

作者:嵌入式開發胖哥

摘要:所謂無規矩不成方圓。任何團隊,規範都是怎麼也繞不開的話題。特别是在我們搞嵌入式C開發的,代碼規範乃是開發的重中之重。有太多的理由去做規範,因為每個人的代碼編寫喜好不同,代碼風格也迥然不同。每一個程式員心目中對好代碼都有自己的主見,統一的編碼規範可以像秦始皇統一戰國一樣,避免不必要的論戰和争議。

寫碼如寫詩!嵌入式C代碼規範有多重要?

有時候會幫同學看一下代碼,發現大多寫代碼都是随心所欲。看起開非常頭大,雖然C語言很重要,但是C語言代碼規範也非常重要。今天分享的就是安富萊C語言編碼規範,建議初學者去官網看看他們的代碼,非常标準!

一、檔案與目錄

1、檔案及目錄的命名規定可用的字元集是A-Z;a-z;0-9;._-。

2、源檔案名字尾用小寫字母.c和.h。

3、檔案的命名要準确清晰地表達其内容,同時檔案名應該精練,防止檔案名過長而造成使用不便。在檔案名中可以适當地使用縮寫。

以下提供兩種命名方式以供參考:(1)各程式子產品的檔案命名開頭 2 個小寫字母代表本子產品的功能:如:主要程式為mpMain.c,mpDisp.c … (2)不寫子產品功能辨別:如:主要程式為Main.c,Disp.c …

4、一個軟體包或一個邏輯元件的所有頭檔案和源檔案建議放在一個單獨的目錄下,這樣有利于查找并使用相關的檔案,有利于簡化一些編譯工具的設定。

5、對于整個項目需要的公共頭檔案,應存放在一個單獨的目錄下(例如:myProject/include)下,可避免其他編寫人引用時目錄太過分散的問題。

6、對于源碼檔案中的段落安排,我們建議按如下的順序排列:

  • a. 檔案頭注釋
  • b. 防止重複引用頭檔案的設定
  • c. #include 部分
  • d. #define 部分
  • e. enum 常量聲明
  • f. 類型聲明和定義,包括 struct、union、typedef 等
  • g. 全局變量聲明
  • h. 檔案級變量聲明
  • i. 全局或檔案級函數聲明
  • j. 函數實作。按函數聲明的順序排列
  • k. 檔案尾注釋

7、在引用頭檔案時,不要使用絕對路徑。如果使用絕對路徑,當需要移動目錄時,必須修改所有相關代碼,繁瑣且不安全;使用相對路徑,當需要移動目錄時,隻需修改編譯器的某個選項即可。

#include “/project/inc/hello.h” /* 不應使用絕對路徑 */
#include “../inc/hello.h” /* 可以使用相對路徑 */
           

8、在引用頭檔案時,使用 <> 來引用預定義或者特定目錄的頭檔案,使用 “” 來引用目前目錄或者路徑相對于目前目錄的頭檔案。

#include <stdio.h> /* 标準頭檔案 */
#include <projdefs.h> /* 工程指定目錄頭檔案 */
#include “global.h” /* 目前目錄頭檔案 */
#include “inc/config.h” /* 路徑相對于目前目錄的頭檔案 */
           

9、為了防止頭檔案被重複引用,應當用ifndef/define/endif結構産生預處理塊。

#ifndef __DISP_H /* 檔案名前名加兩個下劃線“__”,後面加 “_H”
#define __DISP_H
...
...
#endif
           

10、頭檔案中隻存放“聲明”而不存放“定義”,通過這種方式可以避免重複定義。

/* 子產品 1 頭檔案:module1.h */
extern int a = 5; /* 在子產品 1 的 .h 檔案中聲明變量 */

/* 子產品 1 實作檔案:module1.c */
uint8_t g_ucPara; /* 在子產品 1 的 .h 檔案中定義全局變量 g_ucPara */
           

11、如果其它子產品需要引用全局變量g_ucPara, 隻需要在檔案開頭包含module1.h

/* 子產品 2 實作檔案:module2.c */
#include “module1.h” /* 在子產品 2 中包含子產品 1 的 .h 檔案 */
......
g_ucPara = 0;
......
           

12、對于檔案的長度沒有非常嚴格的要求,但應盡量避免檔案過長。一般來說,檔案長度應盡量保持在1000行之内。

二、排版

1、程式塊要采用縮進風格編寫,縮進的空格數為 4 個。

2、相對獨立的程式塊之間、變量說明之後必須加空行。

void DemoFunc(void)

{

uint8_t i;

<---- 局部變量和語句間空一行

/* 功能塊 1 */

for (i = 0; i < 10; i++)

{

...

}

<---- 不同的功能塊間空一行

/* 功能塊 2 */

for (i = 0; i < 20; i++)

{

...

}

}
           

3、作符處劃分新行,操作符放在新行之首,劃分出的新行要進行适當的縮進,使排版整齊,語句可讀。

if ((ucParam1 == 0) && (ucParam2 == 0) && (ucParam3 == 0)

|| (ucParam4 == 0)) <---- 長表達式需要換行書寫

{

......

}
           

4、不允許把多個短語句寫在一行中,即一行隻寫一條語句。

rect.length = 0; rect.width = 0; <---- 不正确的寫法

rect.length = 0; <---- 正确的寫法

rect.width = 0;
           

5、對齊使用TAB 鍵,1 個 TAB 對應 4 個字元位。

6、函數或過程的開始、結構的定義及循環、判斷等語句中的代碼都要采用縮進風格,case 語句下的情況處理語句也要遵從語句縮進要求。

7、程式塊的分界符(如大括号‘{’和‘}’ )應各獨占一行并且位于同一列,同時與引用它們的語句左對齊。在函數體的開始、類的定義、結構的定義、枚舉的定義以及 if、for、do、while、switch、case 語句中的程式都要采用如上的縮進方式。對于與規則不一緻的現存代碼,應優先保證同一子產品中的風格一緻性。

for (...) { <---- 不規範的寫法

... /* program code */

}

for (...)

{ <---- 規範的寫法

... /* program code */

}

if (...)

{ <---- 不規範的寫法

... /* program code */

}

if (...)

{ <---- 規範的寫法

... /* program code */

}
           

8、在兩個以上的關鍵字、變量、常量進行對等操作時,它們之間的操作符之前、之後或者前後要加空格;進行非對等操作時,如果是關系密切的立即操作符(如->),後不應加空格。說明:采用這種松散方式編寫代碼的目的是使代碼更加清晰。

由于留白格所産生的清晰性是相對的,是以,在已經非常清晰的語句中沒有必要再留白格,如果語句已足夠清晰則括号内側(即左括号後面和右括号前面)不需要加空格,多重括号間不必加空格,因為在 C語言中括号已經是最清晰的标志了。

在長語句中,如果需要加的空格非常多,那麼應該保持整體清晰,而在局部不加空格。給操作符留白格時不要連續留兩個以上空格。

(1)逗号、分号隻在後面加空格。

int_32 a, b, c;
           

(2)比較操作符,指派操作符"="、 "+=",算術操作符"+"、"%",邏輯操作符"&&"、"&",位域操作符"<<"、"^"等雙目操作符的前後加空格。

if (current_time >= MAX_TIME_VALUE)
a = b + c;
a *= 2;
a = b ^ 2;
           

(3)"!"、"~"、"++"、"--"、"&"(位址運算符)等單目操作符前後不加空格。

*p = 'a'; /* 内容操作"*"與内容之間 */
flag = !isEmpty; /* 非操作"!"與内容之間 */
p = &mem; /* 位址操作"&" 與内容之間 */
i++; /* "++","--"與内容之間 */
           

(4)"->"、"."前後不加空格。

p->id = pid; /* "->"指針前後不加空格 */
           

(5)if、for、while、switch 等與後面的括号間應加空格,使 if 等關鍵字更為突出、明顯,函數名與其後的括号之間不加空格,以與保留字差別開。

if (a >= b && c > d)
           

三、注釋

1、 一般情況下,源程式有效注釋量必須在 20%以上。說明:注釋的原則是有助于對程式的閱讀了解,在該加的地方都加,注釋不宜太多也不能太少,注釋語言必須準确、易懂、簡潔。

2、在檔案的開始部分,應該給出關于檔案版權、内容簡介、修改曆史等項目的說明。具體的格式請參見如下的說明。在建立代碼和每次更新代碼時,都必須在檔案的曆史記錄中标注版本号、日期、作者、更改說明等項目。其中的版本号的格式為兩個數字字元和一個英文字母字元。數字字元表示大的改變,英文字元表示小的修改。如果有必要,還應該對其它的注釋内容也進行同步的更改。注意:注釋第一行星号要求為 76 個,結尾行星号為 1 個。

/****************************************************************************

* Copyright (C), 2010-2011,武漢xxx系統有限責任公司

* 檔案名: main.c

* 内容簡述:

*

* 檔案曆史:

* 版本号 日期 作者 說明

* 01a 2020-07-29 xxx 建立該檔案

* 01b 2020-08-20 xxx 改為可以在字元串中發送回車符

* 02a 2020-12-03 xxx 增加檔案頭注釋

*/
           

3、對于函數,在函數實作之前,應該給出和函數的實作相關的足夠而精練的注釋資訊。内容包括本函數功能介紹,調用的變量、常量說明,形參說明,特别是全局、全程或靜态變量(慎用靜态變量),要求對其初值,調用後的預期值作詳細的闡述。具體的書寫格式和包含的各項内容請參見如下的例子。

下面這段函數的注釋比較标準,當然,并不局限于此格式,但上述資訊建議要包含在内。

/****************************************************************************

* 函數名: SendToCard()

* 功 能: 向讀卡器發指令,如果讀卡器進入休眠,則首先喚醒它

* 輸 入: 全局變量 gaTxCard[]存放待發的資料

* 全局變量 gbTxCardLen 存放長度

* 輸 出: 無

*/
           

4、邊寫代碼邊注釋,修改代碼同時修改相應的注釋,以保證注釋與代碼的一緻性。不再有用的注釋要删除。

5、注釋的内容要清楚、明了,含義準确,防止注釋二義性。說明:錯誤的注釋不但無益反而有害。注釋主要闡述代碼做了什麼(What),或者如果有必要的話,闡述為什麼要這麼做(Why),注釋并不是用來闡述它究竟是如何實作算法(How)的。

6、避免在注釋中使用縮寫,特别是非常用縮寫。說明:在使用縮寫時或之前,應對縮寫進行必要的說明。

7、注釋應與其描述的代碼靠近,對代碼的注釋應放在其上方或右方(對單條語句的注釋)相鄰位置,不可放在下面,如放于上方則需與其上面的代碼用空行隔開。

例 1:不規範的寫法

/* 擷取複本子系統索引和網絡訓示器 */

<---- 不規範的寫法,此處不應該空行

repssn_ind = ssn_data[index].repssn_index;

repssn_ni = ssn_data[index].ni;
           

例 2:不規範的寫法

repssn_ind = ssn_data[index].repssn_index;

repssn_ni = ssn_data[index].ni;

/* 擷取複本子系統索引和網絡訓示器 */ <---- 不規範的寫法,應該在語句前注釋
           

例 3:規範的寫法

/* 擷取複本子系統索引和網絡訓示器 */

repssn_ind = ssn_data[index].repssn_index;

repssn_ni = ssn_data[index].ni;
           

例 4:不規範的寫法,顯得代碼過于緊湊

/* code one comments */

program code one

/* code two comments */ <---- 不規範的寫法,兩段代碼之間需要加空行

program code two
           

例 5:規範的寫法

/* code one comments */

program code one

/* code two comments */

program code two
           

8、注釋與所描述内容進行同樣的縮排。說明:可使程式排版整齊,并友善注釋的閱讀與了解。

例 1:如下例子,排版不整齊,閱讀稍感不友善。

void example_fun( void )

{

/* code one comments */ <---- 不規範的寫法,注釋和代碼應該相同的縮進

CodeBlock One

/* code two comments */ <---- 不規範的寫法,注釋和代碼應該相同的縮進

CodeBlock Two

}
           

例 2:正确的布局。

void example_fun( void )

{

/* code one comments */

CodeBlock One

/* code two comments */

CodeBlock Two

}
           

9、對變量的定義和分支語句(條件分支、循環語句等)必須編寫注釋。說明:這些語句往往是程式實作某一特定功能的關鍵,對于維護人員來說,良好的注釋幫助更好的了解程式,有時甚至優于看設計文檔。

寫碼如寫詩!嵌入式C代碼規範有多重要?

3年嵌入式物聯網學習資源整理分享:C語言、Linux開發、資料結構;軟體開發,STM32單片機、ARM硬體開發、物聯網通信開發、綜合項目開發教程資料;筆試面試真題。點選下方插件免費領取↓↓↓嵌入式物聯網學習資料(頭條)

10、對于switch語句下的case語句,如果因為特殊情況需要處理完一個case後進入下一個case 處理,必須在該case語句處理完、下一個case語句前加上明确的注釋。說明:這樣比較清楚程式編寫者的意圖,有效防止無故遺漏 break 語句。

11、注釋格式盡量統一,建議使用“/* …… */”,因為 C++注釋“//”并不被所有 C 編譯器支援。

12、注釋應考慮程式易讀及外觀排版的因素,使用的語言若是中、英兼有的,建議多使用中文,除非能非常流利準确的用英文表達。說明:注釋語言不統一,影響程式易讀性和外觀排版,出于對維護人員的考慮,建議使用中文。

13、辨別符的命名要清晰、明了,有明确含義,同時使用完整的單詞或大家基本可以了解的縮寫,避免使人産生誤解。

說明:較短的單詞可通過去掉“元音”形成縮寫;較長的單詞可取單詞的頭幾個字母形成縮寫;一些單詞有大家公認的縮寫。示例:如下單詞的縮寫能夠被大家基本認可。

temp 可縮寫為 tmp;
flag 可縮寫為 flg;
statistic 可縮寫為 stat;
increment 可縮寫為 inc;
message 可縮寫為 msg;
           

14、命名中若使用特殊約定或縮寫,則要有注釋說明。說明:應該在源檔案的開始之處,對檔案中所使用的縮寫或約定,特别是特殊的縮寫,進行必要的注釋說明。

15、自己特有的命名風格,要自始至終保持一緻,不可來回變化。說明:個人的命名風格,在符合所在項目組或産品組的命名規則的前提下,才可使用。(即命名規則中沒有規定到的地方才可有個人命名風格)。

16、對于變量命名,禁止取單個字元(如 i、j、k...),建議除了要有具體含義外,還能表明其變量類型、資料類型等,但 i、j、k 作局部循環變量是允許的。說明:變量,尤其是局部變量,如果用單個字元表示,很容易敲錯(如i寫成j),而編譯時又檢查不出來,有可能為了這個小小的錯誤而花費大量的查錯時間。

17、命名規範必須與所使用的系統風格保持一緻,并在同一項目中統一,比如采用 UNIX 的全小寫加下劃線的風格或大小寫混排的方式,不要使用大小寫與下劃線混排的方式,用作特殊辨別如辨別成員變量或全局變量的 m_和 g_,其後加上大小寫混排的方式是允許的。

示例:Add_User不允許,add_user、AddUser、m_AddUser允許。

18、 除非必要,不要用數字或較奇怪的字元來定義辨別符。 示例:如下命名,使人産生疑惑。

uint8_t dat01;

void Set00(uint_8 c);
           

應改為有意義的單詞命名。

uint8_t ucWidth;

void SetParam(uint_8 _ucValue);
           

19、在同一軟體産品内,應規劃好接口部分辨別符(變量、結構、函數及常量)的命名,防止編譯、連結時産生沖突。說明:對接口部分的辨別符應該有更嚴格限制,防止沖突。如可規定接口部分的變量與常量之前加上“子產品”辨別等。

20、除了編譯開關/頭檔案等特殊應用,應避免使用_EXAMPLE_TEST_之類以下劃線開始和結尾的定義。

四、變量、結構、常量、宏

1、為了友善書寫及記憶,變量類型采用如下重定義:

typedef unsigned char uint8_t;

typedef unsigned short uint16_t;

typedef unsigned long int uint32_t;

typedef signed char int8_t;

typedef signed short int16_t;

typedef signed long int int32_t;

#define __IO volatile
           

2、常見類型的字首

(1)對于一些常見類型的變量,應在其名字前标注表示其類型的字首。字首用小寫字母表示。字首的使用請參照下清單格中說明。(2)對于幾種變量類型組合,字首可以疊加。

3、變量作用域的字首

為了清晰的辨別變量的作用域,減少發生命名沖突,應該在變量類型字首之前再加上表示變量作用域的字首,并在變量類型字首和變量作用域字首之間用下劃線-隔開。

(1)對于全局變量(global variable),在其名稱前加g和變量類型符号字首。

uint32_t g_ulParaWord;

uint8_t g_ucByte;
           

(2)對于靜态變量(static variable),在其名稱前加“s”和變量類型符号字首。

static uint32_t s_ulParaWord;

static uint8_t s_ucByte;
           

(3)函數内部等局部變量前不加作用域字首。(4)對于常量,當可能發生作用域和名字沖突問題時,以上幾條規則對于常量同樣适用。注意,雖然常量名的核心部分全部大寫,但此時常量的字首仍然用小寫字母,以保持字首的一緻性。

4、對于結構體命名類型,表示類型的名字,所有名字以小寫字母tag開頭,之後每個英文單詞的第一個字母大寫(包括第一個單詞的第一個字母),其他字母小寫,結尾_T辨別。單詞之間不使用下劃線分隔,結構體變量以t開頭。

/* 結構體命名類型名 */

typedef struct tagBillQuery_T

{

...

}BillQuery_T;

/* 結構體變量定義 */

BillQuery_T tBillQuery;

對于枚舉定義全部采用大寫,結尾_E辨別。

typedef enum

{

KB_F1 = 0, /* F1 鍵代碼 */

KB_F2, /* F2 鍵代碼 */

KB_F3 /* F3 鍵代碼 */

}KEY_CODE_E;
           

5、常量、宏、模版的名字應該全部大寫。如果這些名字由多個單詞組成,則單詞之間用下劃線分隔。宏指所有用宏形式定義的名字,包括常量類和函數類;常量也包括枚舉中的常量成員。

#define LOG_BUF_SIZE 8000
           

6、不推薦使用位域。

五、函數

1、函數的命名規則。每一個函數名字首需包含子產品名,子產品名為小寫,與函數名差別開。如:uartReceive(序列槽接收)備注:對于非常簡單的程式,可以不加子產品名。

2、函數的的形參需另啟一行,在後面給予說明,形參都以下劃線_開頭,已示與普通變量進行區分,對于沒有形參為空的函數(void)括号緊跟函數後面。

/******************************************************************************

* 函數名:uartConvUartBaud

* 功 能:波特率轉換

* 輸 入: _ulBaud : 波特率

* 輸 出:無

* 返 回:uint32- 轉換後的波特率值

*/

uint32_t uartConvUartBaud(uint32_t _ulBaud)

{

uint32_t ulBaud;

ulBaud = ulBaud * 2; /* 計算波特率 */

......

return ulBaud;

}
           

複制代碼

3、一個函數僅完成一件功能。

4、函數名應準确描述函數的功能。避免使用無意義或含義不清的動詞為函數命名。使用動賓詞組為執行某操作的函數命名。說明:避免用含義不清的動詞如process、handle等為函數命名,因為這些動詞并沒有說明要具體做什麼。示例:參照如下方式命名函數。

void PrintRecord(uint32_t _RecInd);

int32 InputRecord(void);

uint8_t GetCurrentColor(void);
           

5、檢查函數所有參數輸入的有效性。說明:如果約定由調用方檢查參數輸入,則應使用assert()之類的宏,來驗證所有參數輸入的有效性。

6、檢查函數所有非參數輸入的有效性,如資料檔案、公共變量等。說明:函數的輸入主要有兩種:一種是參數輸入;另一種是全局變量、資料檔案的輸入,即非參數輸入。函數在使用輸入之前,應進行必要的檢查。

7、防止将函數的參數作為工作變量。說明:将函數的參數作為工作變量,有可能錯誤地改變參數内容,是以很危險。對必須改變的參數,最好先用局部變量代之,最後再将該局部變量的内容賦給該參數。

8、避免設計五個以上參數函數,不使用的參數從接口中去掉。說明:目的減少函數間接口的複雜度,複雜的參數可以使用結構傳遞。

9、在調用函數填寫參數時,應盡量減少沒有必要的預設資料類型轉換或強制資料類型轉換。說明:因為資料類型轉換或多或少存在危險。

10、避免使用 BOOL 參數。說明:原因有二,其一是BOOL參數值無意義,TURE/FALSE的含義是非常模糊的,在調用時很難知道該參數到底傳達的是什麼意思;其二是BOOL參數值不利于擴充。還有NULL也是一個無意義的單詞。

11、函數的傳回值要清楚、明了。除非必要,最好不要把與函數傳回值類型不同的變量,以編譯系統預設的轉換方式或強制的轉換方式作為傳回值傳回。

12、防止把沒有關聯的語句放到一個函數中。說明:防止函數或過程内出現随機内聚。随機内聚是指将沒有關聯或關聯很弱的語句放到同一個函數或過程中。随機内聚給函數或過程的維護、測試及以後的更新等造成了不便,同時也使函數或過程的功能不明确。使用随機内聚函數,常常容易出現在一種應用場合需要改進此函數,而另一種應用場合又不允許這種改進,進而陷入困境。

在程式設計時,經常遇到在不同函數中使用相同的代碼,許多開發人員都願把這些代碼提出來,并構成一個新函數。若這些代碼關聯較大并且是完成一個功能的,那麼這種構造是合理的,否則這種構造将産生随機内聚的函數。示例:如下函數就是一種随機内聚。

void InitVar( void )

{

Rect.length = 0;

Rect.width = 0; /* 初始化矩形的長與寬 */

Point.x = 10;

Point.y = 10; /* 初始化“點”的坐标 */

}
           

矩形的長、寬與點的坐标基本沒有任何關系,故以上函數是随機内聚。應如下分為兩個函數:

void InitRect( void )

{

Rect.length = 0;

Rect.width = 0; /* 初始化矩形的長與寬 */

}

void InitPoint( void )

{

Point.x = 10;

Point.y = 10; /* 初始化“點”的坐标 */

}
           

13、減少函數本身或函數間的遞歸調用。

說明:遞歸調用特别是函數間的遞歸調用(如A->B->C->A),影響程式的可了解性;遞歸調用一般都占用較多的系統資源(如棧空間);遞歸調用對程式的測試有一定影響。故除非為某些算法或功能的實作友善,應減少沒必要的遞歸調用。

14、改進子產品中函數的結構,降低函數間的耦合度,并提高函數的獨立性以及代碼可讀性、效率和可維護性。優化函數結構時,要遵守以下原則:

  1. 能影響子產品功能的實作。
  2. 仔細考查子產品或函數出錯處理及子產品的性能要求并進行完善。
  3. 通過分解或合并函數來改進軟體結構。
  4. 考查函數的規模,過大的要進行分解。
  5. 降低函數間接口的複雜度。
  6. 不同層次的函數調用要有較合理的扇入、扇出。
  7. 函數功能應可預測。
  8. 提高函數内聚。(單一功能的函數内聚最高)

說明:對初步劃分後的函數結構應進行改進、優化,使之更為合理。

寫碼如寫詩!嵌入式C代碼規範有多重要?

原文作者:果果小師弟

原文标題:寫碼如寫詩!嵌入式C代碼規範有多重要?

原文連結:https://mp.weixin.qq.com/s/UBm5-PiCDgHv6K8o82BSeA

繼續閱讀