關(guān)注、星標(biāo)公眾號 ,直達(dá)精彩內(nèi)容 來源:嵌入式大雜燴 作者:老外 分享GitHub上一位老外的嵌入式C 編碼 規(guī)范(收藏細(xì)讀) [1] 01 ?最重要的規(guī)則??
編 寫代碼時最重要的一條規(guī)則是:檢查周圍的代碼并嘗試模仿它。??作為維護(hù)人員,如果收到的補丁明顯與周圍代碼的
編碼 風(fēng)格不同,這是令人沮喪的。這是不尊重人的,就像某人穿著泥濘的鞋子走進(jìn)一間一塵不染的房子。??因此,無論本文推薦的是什么,如果已經(jīng)編寫了代碼并且您正在對其進(jìn)行修補,請保持其當(dāng)前的樣式一致,即使它不是您最喜歡的樣式。
02 ?一般性的規(guī)則??
這 里列出了最明顯和最重要的一般規(guī)則。在你繼續(xù)閱讀其他章節(jié)之前,請仔細(xì)檢查它們。
使用C99標(biāo)準(zhǔn) 不使用制表符,而是使用空格 每個縮進(jìn)級別使用4個空格 在關(guān)鍵字和左括號之間使用一個空格 在函數(shù)名和左括號之間不要使用空格 int32_t?a?=?sum(4 ,?3 );??????????????/*?OK?*/ int32_t?a?=?sum?(4 ,?3 );?????????????/*?Wrong?*/不要在變量/函數(shù)/宏/類型中使用_或前綴。這是為C語言本身保留的 對于嚴(yán)格的模塊私有函數(shù),使用prv_?name前綴 對于包含下劃線_?char的變量/函數(shù)/宏/類型,只能使用小寫字母 左花括號總是與關(guān)鍵字(for,?while,?do,?switch,?if,…)在同一行 size_t?i;for ?(i?=?0 ;?i?5 ;? i)?{???????????/*?OK?*/ }for ?(i?=?0 ;?i?5 ;? i){????????????/*?Wrong?*/ }for ?(i?=?0 ;?i?5 ;? i)?????????????/*?Wrong?*/ { }int32_t?a; a?=?3 ? ?4 ;??????????????/*?OK?*/for ?(a?=?0 ;?a?5 ;? a)?/*?OK?*/ a=3 4 ;??????????????????/*?Wrong?*/ a?=?3 4 ;????????????????/*?Wrong?*/for ?(a=0 ;a<5 ; a)???????/*?Wrong?*/func_name(5 ,?4 );????????/*?OK?*/ func_name(4 ,3 );?????????/*?Wrong?*/不要初始化靜態(tài)和全局變量為0(或NULL),讓編譯器為您做 static?int32_t?a;???????/*?OK?*/ static?int32_t?b?=?4 ;???/*?OK?*/ static?int32_t?a?=?0 ;???/*?Wrong?*/ void?my_func(void)?{ ????static?int32_t*?ptr;/*?OK?*/ ????static?char?abc?=?0 ;/*?Wrong?*/ }void?my_func(void)?{ ????char?a;?????????????/*?OK?*/ ????char?b;?????????????/*?Wrong,?variable?with ?char?type?already?exists?*/ ????char?a,?b;??????????/*?OK?*/ }??i. 自定義結(jié)構(gòu)和枚舉??ii. 整數(shù)類型,更寬的無符號類型優(yōu)先??iii. 單/雙浮點
int?my_func(void)?{ ????/*?1 ?*/ ????my_struct_t?my;?????/*?First?custom?structures?*/ ????my_struct_ptr_t*?p;?/*?Pointers?too?*/ ????/*?2 ?*/ ????uint32_t?a; ????int32_t?b; ????uint16_t?c; ????int16_t?g; ????char?h; ????/*?...?*/ ????/*?3 ?*/ ????double?d; ????float?f; }總是在塊的開頭聲明局部變量,在第一個可執(zhí)行語句之前 在for循環(huán)中聲明計數(shù)器變量 /*?OK?*/for ?(size_t?i?=?0 ;?i?10 ;? i) /*?OK,?if ?you?need?counter?variable?later?*/ size_t?i;for ?(i?=?0 ;?i?10 ;? i)?{ ????if ?(...)?{ ????????break ; ????} }if ?(i?*?10 )?{ } /*?Wrong?*/ size_t?i;for ?(i?=?0 ;?i?10 ;? i)?...避免在聲明中使用函數(shù)調(diào)用來賦值變量,除了單個變量 void?a(void)?{ ????/*?Avoid?function?calls?when?declaring?variable?*/ ????int32_t?a,?b?=?sum(1 ,?2 ); ????/*?Use?this?*/ ????int32_t?a,?b; ????b?=?sum(1 ,?2 ); ????/*?This?is ?ok?*/ ????uint8_t?a?=?3 ,?b?=?4 ; }除了char、float或double之外,始終使用stdint.h標(biāo)準(zhǔn)庫中聲明的類型。例如,8位的uint8_t等 不要使用stdbool.h庫。分別使用1或0表示真或假 /*?OK?*/ uint8_t?status; status?=?0 ; /*?Wrong?*/#include? bool?status?=?true;永遠(yuǎn)不要與真實相比較。例如,使用if(check_func()){…}替換if (check_func() * 1) 總是將指針與空值進(jìn)行比較 void*?ptr; /*?...?*/ /*?OK,?compare?against?NULL?*/if ?(ptr?*?NULL?||?ptr?!=?NULL)?{ } /*?Wrong?*/if ?(ptr?||?!ptr)?{ }int32_t?a?=?0 ; ... a ;????????????/*?Wrong?*/ a;????????????/*?OK?*/for ?(size_t?j?=?0 ;?j?10 ;? j)?{}??/*?OK?*/總是使用size_t作為長度或大小變量 如果函數(shù)不應(yīng)該修改指針?biāo)赶虻膬?nèi)存,則總是使用const作為指針 如果不應(yīng)該修改函數(shù)的形參或變量,則總是使用const /*?When?d?could?be?modified,?data?pointed?to?by?d?could?not ?be?modified?*/ void my_func(const?void*?d)?{ } /*?When?d?and ?data?pointed?to?by?d?both?could?not ?be?modified?*/ void my_func(const?void*?const?d)?{ } /*?Not?required,?it?is ?advised?*/ void my_func(const?size_t?len)?{ } /*?When?d?should?not ?be?modified?inside?function,?only?data?pointed?to?by?d?could?be?modified?*/ void my_func(void*?const?d)?{ }當(dāng)函數(shù)可以接受任何類型的指針時,總是使用void?*,不要使用uint8_t?*。函數(shù)在實現(xiàn)時必須注意正確的類型轉(zhuǎn)換 /* ?*?To?send?data,?function?should?not ?modify?memory?pointed?to?by?`data`?variable ?*?thus?`const`?keyword?is ?important ?* ?*?To?send?generic?data?(or ?to?write?them?to?file) ?*?any?type?may?be?passed?for ?data, ?*?thus?use?`void?*` ?*/ /*?OK?example?*/ void?send_data(const?void*?data,?size_t?len)?{?/*?OK?*/ ????/*?Do?not ?cast?`void?*`?or ?`const?void?*`?*/ ????const?uint8_t*?d?=?data;/*?Function?handles?proper?type?for ?internal?usage?*/ } void?send_data(const?void*?data,?int?len)?{????/*?Wrong,?not ?not ?use?int?*/ }總是使用括號和sizeof操作符 不要使用變長數(shù)組。使用動態(tài)內(nèi)存分配代替標(biāo)準(zhǔn)C?malloc和自由函數(shù),或者如果庫/項目提供了自定義內(nèi)存分配,使用它的實現(xiàn)看看LwMEM,一個自定義內(nèi)存管理庫。 /*?OK?*/#include? void my_func(size_t?size)?{ ????int32_t*?arr; ????arr?=?malloc(sizeof(*arr)?*?n);?/*?OK,?Allocate?memory?*/ ????arr?=?malloc(sizeof?*arr?*?n);??/*?Wrong,?brackets?for ?sizeof?operator?are?missing?*/ ????if ?(arr?*?NULL)?{ ????????/*?FAIL,?no?memory?*/ ????} ????free(arr);??/*?Free?memory?after?usage?*/ } /*?Wrong?*/ void my_func(size_t?size)?{ ????int32_t?arr[size];??/*?Wrong,?do?not ?use?VLA?*/ }總是將variable與0進(jìn)行比較,除非它被視為布爾類型 永遠(yuǎn)不要將布爾處理的變量與0或1進(jìn)行比較。用NOT(!)代替 size_t?length?=?5 ;??/*?Counter?variable?*/ uint8_t?is_ok?=?0 ;??/*?Boolean-treated?variable?*/if ?(length)?????????/*?Wrong,?length?is ?not ?treated?as ?boolean?*/if ?(length?>?0 )?????/*?OK,?length?is ?treated?as ?counter?variable?containing?multi?values,?not ?only?0 ?or ?1 ?*/if ?(length?*?0 )????/*?OK,?length?is ?treated?as ?counter?variable?containing?multi?values,?not ?only?0 ?or ?1 ?*/if ?(is_ok)??????????/*?OK,?variable?is ?treated?as ?boolean?*/if ?(!is_ok)?????????/*?OK,?-||-?*/if ?(is_ok?*?1 )?????/*?Wrong,?never?compare?boolean?variable?against?1 !?*/if ?(is_ok?*?0 )?????/*?Wrong,?use?!?for ?negative?check?*/對于注釋,總是使用/*?comment */,即使是單行注釋 在頭文件中總是包含帶有extern關(guān)鍵字的c 檢查 每個函數(shù)都必須包含doxygen-enabled注釋,即使函數(shù)是靜態(tài)的 使用英文名稱/文本的函數(shù),變量,注釋 變量使用小寫字母 如果變量包含多個名稱,請使用下劃線。force_redraw。不要使用forceRedraw 對于C標(biāo)準(zhǔn)庫的包含文件,請始終使用<和>。例如,#?include?stdlib.h?> 對于自定義庫,請始終使用""。例如,#?include“my_library.h” 當(dāng)轉(zhuǎn)換為指針類型時,總是將星號與類型對齊,例如。uint8_t* t = (uint8_t*)var_width_diff_type 始終尊重項目或庫中已經(jīng)使用的代碼風(fēng)格 03 ?注釋相關(guān)的規(guī)則不允許以//開頭的注釋。總是使用② comment */,即使是單行注釋 對于多行注釋,每行使用空格 星號 /* ?*?This?is ?multi-line?comments, ?*?written?in ?2 ?lines?(ok) ?*/ /** ?*?Wrong,?use?double-asterisk?only?for ?doxygen?documentation ?*/ /* *?Single?line?comment?without?space?before?asterisk?(wrong) */ /* ?*?Single?line?comment?in ?multi-line?configuration?(wrong) ?*/ /*?Single?line?comment?(ok)?*/注釋時使用12個縮進(jìn)(12 * 4個空格)偏移量。如果語句大于12個縮進(jìn),將注釋4-空格對齊(下面的例子)到下一個可用縮進(jìn) void?my_func(void)?{ ????char?a,?b; ????a?=?call_func_returning_char_a(a);??????????/*?This?is ?comment?with ?12 *4 ?spaces?indent?from ?beginning?of?line?*/ ????b?=?call_func_returning_char_a_but_func_name_is_very_long(a);???/*?This?is ?comment,?aligned?to?4 -spaces?indent?*/ }04 ?函數(shù)定義的規(guī)則每個可以從模塊外部訪問的函數(shù)都必須包含函數(shù)原型(或聲明) 函數(shù)名必須小寫,可以用下劃線_分隔。(這個原則好像因人而異 ) /*?OK?*/ void?my_func(void); void?myfunc(void); /*?Wrong?*/ void?MYFunc(void); void?myFunc();當(dāng)函數(shù)返回指針時,將星號對齊到返回類型 /*?OK?*/ const?char*?my_func(void); my_struct_t*?my_func(int32_t?a,?int32_t?b); /*?Wrong?*/ const?char?*my_func(void); my_struct_t?*?my_func(void);對齊所有的功能原型(使用相同/相似的功能)以提高可讀性 /*?OK,?function?names?aligned?*/ void????????set(int32_t?a); my_type_t???get(void); my_ptr_t*???get_ptr(void); /*?Wrong?*/ void?set(int32_t?a); const?char?*?get(void);函數(shù)實現(xiàn)必須在單獨的行中包含返回類型和可選的其他關(guān)鍵字 /*?OK?*/ int32_t foo(void)?{ ????return ?0 ; } /*?OK?*/ static?const?char* get_string(void)?{ ????return ?"Hello?world!\r\n" ; } /*?Wrong?*/ int32_t?foo(void)?{ ????return ?0 ; }05 ?變量相關(guān)的規(guī)則/*?OK?*/ int32_t?a; int32_t?my_var; int32_t?myvar; /*?Wrong?*/ int32_t?A; int32_t?myVar; int32_t?MYVar;void?foo(void)?{ ????int32_t?a,?b;???/*?OK?*/ ????char?a; ????char?b;?????????/*?Wrong,?char?type?already?exists?*/ }void?foo(void)?{ ????int32_t?a; ????a?=?bar(); ????int32_t?b;??????/*?Wrong,?there?is ?already?executable?statement?*/ }int32_t?a,?b; a?=?foo();if ?(a)?{ ????int32_t?c,?d;???/*?OK,?c?and ?d?are?in ?if -statement?scope?*/ ????c?=?foo(); ????int32_t?e;??????/*?Wrong,?there?was?already?executable?statement?inside?block?*/ }/*?OK?*/ char*?a; /*?Wrong?*/ char?*a; char?*?a;當(dāng)聲明多個指針變量時,可以使用星號對變量名進(jìn)行聲明 /*?OK?*/ char?*p,?*n;06 ?結(jié)構(gòu)、枚舉類型定義結(jié)構(gòu)名或枚舉名必須小寫,單詞之間有下劃線_字符 結(jié)構(gòu)或枚舉可以包含typedef關(guān)鍵字 所有結(jié)構(gòu)成員都必須小寫 所有枚舉成員必須是大寫的 結(jié)構(gòu)/枚舉必須遵循doxygen文檔語法 在聲明結(jié)構(gòu)體時,它可以使用以下三種不同的選項之一: ??1. 當(dāng)結(jié)構(gòu)體僅用名稱聲明時,它的名稱后不能包含
_t后綴。
struct?struct_name?{ ????char*?a; ????char?b; };??2. 當(dāng)只使用
typedef聲明結(jié)構(gòu)時,它的名稱后面必須包含
_t后綴。
typedef?struct?{ ????char*?a; ????char?b; }?struct_name_t;??3. 當(dāng)結(jié)構(gòu)用
name和
typedef聲明時,它不能包含t作為基本名稱,它必須在它的名稱后面包含t后綴作為
typedef部分。
typedef?struct?struct_name?{ ????char*?a; ????char?b; ????char?c; }?struct_name_t;/*?a?and ?b?must?be?separated?to?2 ?lines?*/ /*?Name?of?structure?with ?typedef?must?include?_t?suffix?*/ typedef?struct?{ ????int32_t?a,?b; }?a; /*?Corrected?version?*/ typedef?struct?{ ????int32_t?a; ????int32_t?b; }?a_t; /*?Wrong?name,?it?must?not ?include?_t?suffix?*/ struct?name_t?{ ????int32_t?a; ????int32_t?b; }; /*?Wrong?parameters,?must?be?all?uppercase?*/ typedef?enum?{ ????MY_ENUM_TESTA, ????my_enum_testb, }?my_enum_t;在聲明時初始化結(jié)構(gòu)時,使用C99初始化風(fēng)格 /*?OK?*/ a_t?a?=?{ ????.a?=?4 , ????.b?=?5 , }; /*?Wrong?*/ a_t?a?=?{1 ,?2 };當(dāng)為函數(shù)句柄引入new?typedef時,使用_fn后綴 /*?Function?accepts?2 ?parameters?and ?returns?uint8_t?*/ /*?Name?of?typedef?has?`_fn`?suffix?*/ typedef?uint8_t?(*my_func_typedef_fn)(uint8_t?p1,?const?char*?p2);07 ?復(fù)合語句規(guī)則每個復(fù)合語句必須包括左花括號和右花括號,即使它只包含1個嵌套語句 每個復(fù)合語句必須包含單個縮進(jìn);嵌套語句時,每個嵌套包含1個縮進(jìn)大小 /*?OK?*/if ?(c)?{ ????do_a(); }?else ?{ ????do_b(); } /*?Wrong?*/if ?(c) ????do_a();else ????do_b(); /*?Wrong?*/if ?(c)?do_a();else ?do_b();在if或if-else-if語句的情況下,else必須與第一條語句的右括號在同一行 /*?OK?*/if ?(a)?{ }?else ?if ?(b)?{ }?else ?{ } /*?Wrong?*/if ?(a)?{ }else ?{ } /*?Wrong?*/if ?(a)?{ }else { }在do-while語句的情況下,while部分必須與do部分的右括號在同一行 /*?OK?*/ do?{ ????int32_t?a; ????a?=?do_a(); ????do_b(a); }?while ?(check()); /*?Wrong?*/ do { /*?...?*/ }?while ?(check()); /*?Wrong?*/ do?{ /*?...?*/ }while ?(check());if ?(a)?{ ????do_a(); }?else ?{ ????do_b(); ????if ?(c)?{ ????????do_c(); ????} }不要做沒有花括號的復(fù)合語句,即使是單個語句。下面的例子展示了一些不好的做法 if ?(a)?do_b();else ?do_c();if ?(a)?do_a();?else ?do_b();空while循環(huán)、do-while循環(huán)或for循環(huán)必須包含花括號 /*?OK?*/while ?(is_register_bit_set())?{} /*?Wrong?*/while ?(is_register_bit_set());while ?(is_register_bit_set())?{?}while ?(is_register_bit_set())?{ }如果while(或for、do-while等)為空(嵌入式編程中也可能是這種情況),請使用空的單行括號 /*?Wait?for ?bit?to?be?set?in ?embedded?hardware?unit uint32_t*?addr?=?HW_PERIPH_REGISTER_ADDR; /*?Wait?bit?13 ?to?be?ready?*/while ?(*addr?