嵌入式C語言編程規範(我的規約) 分類: 嵌入式開發學習 2015-06-18 09:51 37人閱讀 評論(0) 收藏

0規範制定說明

0.1箴言

技術人員設計程序的首要目的是用於技術人員溝通和交流,其次纔是用於機器執行。程序的生命力在於用戶使用,程序的成長在於後期的維護及根據用戶需求更新和升級功能。若是你的程序只能由你來維護,當你離開這個程序時,你的程序也和你一塊兒離開了,這將給公司和後來接手的技術人員帶來巨大的痛苦和損失。所以,爲了程序可讀、易理解、好維護,你的程序須要遵照必定的規範,你的程序須要設計。程序員

「程序必須爲閱讀它的人而編寫,只是順便用於機器執行。」算法

              ——Harold Abelson 和 Gerald Jay Sussmanexpress

「編寫程序應該以人爲本,計算機第二。」編程

——Steve McConnell數組

0.2簡介:

爲提升產品代碼質量,指導儀表嵌入式軟件開發人員編寫出簡潔、可維護、可靠、可測試、高效、可移植的代碼,編寫了本規範。  安全

本規範將分爲完整版和精簡版,完整版將包括更多的樣例、規範的解釋以及參考材料(what & why),而精簡版將只包含規則部分(what)以便查閱。 數據結構

在本規範的最後,列出了一些業界比較優秀的編程規範,做爲延伸閱讀參考材料。架構

本規範主要包含如下兩個方面的內容:app

一:爲造成統一編程規範,從編碼形式角度出發,本規範對標示符命名、格式與排版、註釋等方面進行了詳細闡述。ide

二:爲編寫出高質量嵌入式軟件,從嵌入式軟件安全及可靠性出發,本規範對因爲C語言標準、C語言自己、C編譯器及我的理解致使的潛在危險進行說明及規避。

0.3適用範圍:

本規範適用於濟南金鐘電子衡器股份有限公司儀表檯秤產品部嵌入式軟件的開發,也對其餘嵌入式軟件開發起必定的指導做用。

0.4術語定義

0.4.1 規範術語

原則:編程時必須堅持的指導思想。

規則:編程時須要遵循的約定,分爲強制和建議(強制是必須遵照的,建議是通常狀況下須要遵照,但沒有強制性)。

說明:對原則/規則進行必要的解釋。 

實例:對此原則/規則從正、反兩個方面給出例子。

材料:擴展、延伸的閱讀材料。

 

Unspecified:未詳細說明的行爲,這些是必須成功編譯的語言結構,但關於結構的行爲,編譯器的編寫者有某些自由。例如C語言中的「運算次序」問題。這樣的問題有 22 個。 在某種方式上徹底相信編譯器的行爲是不明智的。編譯器的行爲甚至不會在全部可能的結構中都是一致的。

Undefined:未定義行爲,這些是本質的編程錯誤,但編譯器的編寫者不必定爲此給出錯誤信息。相應的例子是無效參數傳遞給函數,或函數的參數與定義時的參數不匹配。從安全性角度這是特別重要的問題,由於它們表明了那些不必定能被編譯器捕捉到的錯誤。

Implementation-defined:實現定義的行爲,這有些相似於「unspecified 」問題,其主要區別在於編譯器要提供一致的行爲並記錄成文檔。換句話說,不一樣的編譯器之間功能可能會有不一樣,使得代碼不具備可移植性,但在任一編譯器內,行爲應當是良好定義的。好比用在一個正整數和一個負整數上的整除運算「/ 」和求模運算符「% 」。存在76個這樣的問題。從安全性角度,假如編譯器徹底地記錄了它的方法並堅持它的實現,那麼它可能不是那樣相當重要。儘量的狀況下要避免這些問題。

0.4.2  C語言相關術語

聲明(declaration):指定了一個變量的標識符,用來描述變量的類型,是類型仍是對象,

者函數等。聲明,用於編譯器(compiler)識別變量名所引用的實體。如下這些就是聲明:

extern int bar;

extern int g(int,int);

double f(int,double);[對於函數聲明,extern關鍵字是能夠省略的。]

定義(definition):是對聲明的實現或者實例化。鏈接器(linker)須要它(定義)來引用內存實體。與上面的聲明相應的定義以下:

int bar;

int g(int lhs,int rhs) {returnlhs*rhs;} 

double f(int i,double d) {returni+d;} 

0.5規則的形式

規則/原則<序號>(規則類型):規則內容。                                 

[原始參考]

<序號>:每條規則都有一個序號,序號是按照章節目錄-**的形式,從數字1開始。例如,若在此章節有個規則的話,序號爲0.5-1。

(規則類型):或者是‘強制’,或者是‘建議’。

規則內容:此條規則的具體內容。

[原始參考]:指示了產生本條款或本組條款的可應用的主要來源。

1標示符命名規則

1.1標示符命名總則

規則1.1-1(強制):標識符(內部的和外部的)的有效字符不能多於31。

[UndefinedImplementation-defined]

 

說明:ISO 標準要求在內部標識符之間前31 個字符必須是不一樣的,外部標識符之間前6 個字符必須是不一樣的(忽略大小寫)以保證可移植性。咱們這裏放寬了此要求,要求內部、外部標示符的有效字符不能多於31便可。這樣主要是便於編譯器識別,代碼清晰易讀,並保證可移植性。

 

規則1.1-2(強制):具備內部做用域的標識符不該使用與具備外部做用域的標識符相同的

名稱,在內部做用域裏具備內部標示符會隱藏外部標識符。

 

說明:外部做用域和內部做用域的定義以下。文件範圍內的標識符能夠看作是具備最外部

(outermost )的做用域;塊範圍內的標識符看作是具備更內部(more inner)的做用域,連續嵌套的塊,其做用域更深刻。若是內部做用域標示符和外部做用域標示符同名,內部做用域標示符會覆蓋外部做用域標示符,致使程序混亂。

實例:

INT8U  test;

{

INT8U  test;      /*定義了兩個test  */    

   test  =  3;      /*這將產生混淆    */     

}

 

規則1.1-3(建議):具備靜態存儲期的對象或函數標識符不能重用。

 

說明:無論做用域如何,具備靜態存儲期的標識符都不該在系統內的全部源文件中重用。它包含帶有外部連接的對象或函數,及帶有靜態存儲類標識符的任何對象或函數。在一個文件中存在一個具備內部連接的標識符,而在另一個文件中存在着具備外部連接的相同名字的標識符,或者存在兩個標示符相同的外部標示符。對用戶來講,這有可能致使混淆。

實例:

          test1.c

/**定義了一個靜態文件域變量test1*/

          static INT8U  test1; 

          

          void test_fun(void)

          {

                   INT8U test1;   /*定義了一個同名的局部變量test1*/

}

test2.c

/**在另外一個文件又定義了一個具備外部連接的文件域變量test1*/

INT8U  test1; 

原則1.1-4(強制):標識符的命名要清晰、明瞭,有明確含義,同時使用完整的單詞或你們基本能夠理解的縮寫,避免令人產生誤解。

 

說明:標示符的命名儘可能作到見名知意,儘可能讓別人快速理解你的代碼。

實例:

           好的命名方法:

          INT8U   debug_message;

          INT16U  err_num;

      很差的命名方法:

          INT8U   dbmesg;

          INT16U   en;

 

原則1.1-5(強制):常見通用的單詞縮寫儘可能統一,不得使用漢語拼音、英語混用。

說明:簡短的單詞可使用略去‘元音’字母造成縮寫,較長的單詞可使用音節首字母

者單詞前幾個字母造成縮寫,針對你們公認的單詞縮寫要統一。對於特定的項目要使

用的專有縮寫應該註明或者作統一說明。

 

實例:常見單詞縮寫表(建議):

單詞

縮寫

單詞

縮寫

argument

arg

buffer

buf

clock

clk

command

cmd

compare

cmp

configuration

cfg

device

dev

error

err

hexadecimal

hex

increment

inc

initialize

init

maximum

max

message

msg

minimum

min

parameter

param

previous

prev

register

reg

semaphore

sem

statistic

stat

synchronize

syn

temp

tmp

 

 

 

原則1.1-6(建議):用正確的反義詞組命名具備互斥意義的變量或相反動做的函數等。

實例:常見反義詞表:

 

正義

反義

正義

反義

add

remove

begin

end

create

destroy

insert

delete

first

last

get

release

increment

decrement

put

get

add

delete

lock

unlock

open

close

min

max

old

new

start

stop

next

previous

source

target

show

hide

send

receive

source

destination

copy

pase

up

down

 

 

 

原則1.1-7(建議):標示符儘可能避免使用數字編號,除非邏輯上須要。

實例:

         #define   DEBUG_0_MSG

         #define   DEBUG_1_MSG

應改成更有意義的定義:

#define   DEBUG_WARN_MSG

         #define   DEBUG_ERR_MSG

 

材料:《代碼大全第2版》(Steve McConnell 著 金戈/湯凌/陳碩/張菲 譯 電子工業出版社

2006年3月)"第11章變量命的力量"。

1.2文件命名及存儲規則

規則1.2-1(強制):文件名使用小寫字母。

說明:因爲不一樣系統對文件名大小寫處理不一樣,Windows不區分文件名大小寫,而Linux區分。因此文件名命名均採用小寫字母,多個單詞之間可以使用」_」分隔符。

實例:disp.h   os_sem.c

 

規則1.2-2(建議):工程源碼使用GB2312編碼方式。

說明:程序裏的註釋可能會使用中文,GB2312是簡體中文編碼,大部分的編輯工具和集成IDE環境都支持GB2312編碼,爲避免中文亂碼,建議使用GB2312對源碼進行編碼。若須要轉換成其餘編碼格式,可以使用文本編碼轉換工具進行轉換。

 

規則1.2-3(強制):工程源碼使用版本管理工具進行版本管理。

說明:程序通常須要大量更新、修正、維護工做,且有時須要多人合做。使用版本管理工具能夠幫助你提升工做效率。建議使用「Git」版本管理工具。

 

1.3變量命名規則

原則1.3-1(強制):變量命名應明確所表明的含義或者狀態。

說明:變量名稱可使用名詞表述清楚的儘可能使用名詞,使用名詞沒法描述清楚時,使用形

      容詞或者描述性的單詞+名詞的形式。變量通常爲實體的屬性、狀態等信息,使用上

      述方案通常能夠解決變量名的命名問題,若是出現命名很困難或者沒法給出合理的命

名方式時,問題可能出如今總體設計上,請從新審視設計。

 

規則1.3-2(強制):全局變量添加」G_」前綴,全局靜態變量添加」 S_ 」,局部靜態變量添加」s_」前綴。使用大小寫混合方式命名,大寫字母用於分割不一樣單詞。

說明:添加前綴的緣由有兩個。首先,使全局變量變得更醒目,提醒技術開發人員使用這些變量時要當心。其次,添加前綴使全局變量和靜態變量變得和其餘變量不一致,提醒技術開發人員儘可能少用全局變量。

實例:

 /**出錯信息       */

INT8U     G_ErrMsg;  

 

/**每秒鐘轉動圈數 */   

static INT32U   S_CirclePerSec;    

 

規則1.3-3(強制):局部變量使用小寫字母,若標示符比較複雜,使用’_’分隔符。

說明:局部變量所有使用小寫字母,和全局變量有明顯區分,使讀者看到標示符就知道是何

      種做用域的變量。

實例:

      INT32U  download_program_address;

 

規則1.3-4(強制):定義指針變量*緊挨變量名,全局指針變量使用大寫P前綴」P_」,局部指針變量使用小寫p前綴」p _」。

實例:  INT8U  *P_MsgAddress;          /*全局變量*/

INT8U  *p_msg;                                  /*局部變量*/

 

1.4函數命名規則

原則1.4-1(強制):函數命名應該明確針對什麼對象作出了什麼操做。

說明:函數的功能是獲取、修改實體的屬性、狀態等,採用「動詞+名詞」的方式能夠知足上述需求,若出現使用此方式命名函數很困難或不能命名的狀況,問題可能出如今總體設計上,請從新審視設計方案。

 

規則1.4-2(強制):具備外部連接的函數命名使用大小寫混合的方式,首字母大寫,用於分割不一樣單詞。

說明:函數具備外部連接屬性的含義是函數經過頭文件對外聲明後,對其餘文件或模塊來

說是可見的。若是一個函數要在其餘模塊或者文件中使用,須要在頭文件中聲明該函

數。另外,在頭文件聲明函數,還能夠促使編譯器檢查函數聲明和調用的一致性。

實例:

     char  *GetErrMsg(ErrMsg  *msg);

 

規則1.4-3(強制):具備文件內部連接屬性的函數命名使用小寫字母,使用’_’分隔符分割不一樣單詞,且使用static關鍵字限制函數做用域。

說明:函數具備內部連接屬性的含義是函數只能在模塊或文件內部調用,對文件或模塊外來

說是不可見的。若是一個函數僅在模塊內部或者文件內部使用,須要限制函數連接圍,

使用static修飾符修飾函數,使其只具備內部連接屬性。在源文件中聲明一遍具備內

部連接的函數一樣具備促使編譯器檢查函數聲明和調用的一致性。

 

實例:

static  char  get_key(void);

 

規則1.4-4(強制):函數參數使用小寫字母,各單詞之間使用「_」分割,儘可能保持參數順序從左到右爲:輸入、修改、輸出。

說明:函數參數順序爲需輸入參數值(這個值通常不修改,若不須要修改使用const關鍵字

修飾),需修改的參數(這個參數輸入後用於提供數據,函數內部能夠修改此參數),

輸出參數(這個參數是函數輸出值)。

1.5常量的命名規則

規則1.5-1(強制):常量(#define定義的常量、枚舉、const定義的常量)的定義使用全大寫字母,單詞之間加 ’_’分割的命名方式。

實例:

      #define    PI_ROUNDED   3.14

const double PI_ROUNDED = 3.14;

enum      weekday{ SUN,MON,TUE,WED,THU,FRI,SAT };

 

規則1.5-2(建議):常數宏定義時,十六進制數的表示方法爲0xFF。

說明:前面0x中的x小寫,數據中的」A-F」大寫。

 

1.6新定義的類型命名規範

規則1.6-1(強制):新定義類型名的命名應該明確抽象對象的含義,新類型名使用大寫字母,單詞之間加’_’分割,新類型指針在類型名前增長前綴」P_」。成員變量標示符前加類型名稱前綴,首字母大寫用於區分各個單詞。

實例:typedef struct  _STUDENT

{

    StudentName;

      StudentAge  ;

    ......

}STUDENT , *P_ STUDENT;/* STUDENT 爲新類型名稱,P_ STUDENT 爲新類型指針名*/

2外觀佈局

2.1排版與格式

2.1.1 頭文件排版

規則2.1.1-1(強制):頭文件排版內容依次爲包含的頭文件、宏定義、類型定義、聲明變量、聲明函數。且各個種類的內容間空三行。

說明:頭文件是模塊對外的公用接口。在頭文件中定義的宏,能夠被其餘模塊引用。Project

      中不建議使用所有變量,若使用則需在頭文件裏對外聲明。模塊對外的函數接口在模

      塊頭文件裏聲明。

2.1.2 源文件排版

規則2.1.2-1(強制):源文件排版內容依次爲包含的頭文件、宏定義、具備外部連接屬性的全局變量定義、模塊內部使用的static變量、具備內部連接的函數聲明、函數實現代碼。且各個種類的內容間空三行。

說明:模塊內部定義的宏,只能在該模塊內部使用。只在模塊內部使用的函數,需在源碼文

      件中聲明,用於促使編譯器檢查函數聲明和調用的一致性。

 

規則2.1.2-2(強制):程序塊採用縮進風格編寫,每級縮進4個空格。

說明:當前主流IDE都支持Tab縮進,使用Tab縮進須要打開和設置相關選項。宏定義、編譯開關、條件預處理語句能夠頂格。

 

規則2.1.2-3(強制):if、for、do、while、case、switch、defaul、typedef等語句獨佔一行,且這些關鍵字後需空一格。

說明:執行語句必須用縮進風格寫,屬於iffordowhilecaseswitchdefaulttypedef

等的下一個縮進級別。通常寫iffordowhile等語句都會有成對出現的{}‟,if

fordowhile等語句後的執行語句建議增長成對的「{}」; 若是if/else語句塊中只

有一條語句,也需增長「{}」。

實例:

 

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

{

for  (j  =  0;  j  <  max_num; j++)

{

        If (name_found)

                  {

                                                        語句

}

else

{

              語句

}

}

}

 

規則2.1.2-4(強制):進行雙目運算、賦值時,操做符以前、以後要加空格;進行非對等操做時,若是是關係密切的當即操做符(如->),後不該加空格。

說明:採用這種方式書寫代碼,主要目的是使代碼更清晰,使關鍵操做符更突出

實例:

(1)    比較操做符, 賦值操做符"="、 "+=",算術操做符"+"、"%",邏輯操做符"&&"、"&",

位域操做符"<<"、"^"等雙目操做符的先後加空格。

If (a > b)

a += 2;

b = a ^ 3;

(2)    "!"、"~"、"++"、"--"、"&"(地址操做符)等單目操做符先後不加空格。

Search_dowm = !true;

a++;

(3)    "->"、"."、」[]」先後不加空格。

Weight         =  G_Car->weight;

eye           =  People.eye;

array[8]        =  8;

 

規則2.1.2-5(建議):一行只定義一個變量,一行只書寫一條執行語句,多行同類操做時操做符儘可能保持對齊。

說明:一行定義一個變量,一行只書寫一條執行語句,方便註釋,多行同類操做對齊美觀、整潔。

實例:

events_rdy        =  OS_FALSE;

events_rdy_nbr =  0;

events_stat    =  OS_STAT_RDY;

pevents           =  pevents_pend;

pevent            = *pevents;

 

規則2.1.2-6(建議):函數內部局部變量定義和函數語句之間應空三行。

說明:局部變量定義和函數語句是相對獨立的,並且空三行能夠更清晰地表示出這種獨立性。

 

3註釋

3.1註釋原則

原則3.1-1(強制):註釋的內容要清楚、明瞭,含義準確,在代碼的功能、意圖層次上進行註釋。

說明:註釋的目的是讓讀者快速理解代碼的意圖。註釋不是爲了名詞解釋(what),而是說

明用途(why)。

實例:

          以下注釋純屬多餘:

                   ++i;                                     // i增長1                                   

if (data_ready)                          /* 若是data_ready爲真 */

以下注釋無任何參考價值:

// 時間有限,如今是:04,根原本不及想爲何,也沒人能幫我說清楚

 

原則3.1-2(強制):註釋應分爲兩個角度進行,首先是應用角度,主要是告訴使用者如何使用接口(即你提供的函數),其次是實現角度,主要是告訴後期升級、維護的技術人員實現的原理和細節。

說明:每個產品均可以分爲三個層次,產品自己是一個層次,這個層次之下的是你使用

的更小的組件,這個層次之上的是你爲別人提供的服務。你這個產品的存在的價值

就在於把最底層的小部件的使用細節隱藏,同時給最上層的用戶提供方便、簡潔的

使用接口,知足用於需求。從這個角度來看軟件的註釋,你應該時刻想着你寫的註釋

是給那一層次的人員看的,若是是用戶,那麼你應該注重描述如何使用,若是是後期

維護者,那麼你應該注重原理和實現細節。

 

原則3.1-3(強制):修改代碼時,應維護代碼周邊的註釋,使其代碼和註釋一致,再也不使用的註釋應刪除。

說明:註釋的目的在於幫助讀者快速理解代碼使用方法或者實現細節,若註釋和代碼不一

      致,會起到相反的做用。建議在修改代碼前應該先修改註釋。

 

規則3.1-4(建議):代碼段不該被「註釋掉」(comment out )。

說明:當源代碼段不須要被編譯時,應該使用條件編譯來完成(如帶有註釋的#if或#ifdef 結

構)。爲這種目的使用註釋的開始和結束標記是危險的,由於C 不支持/**/嵌套的注

釋,並且已經存在於代碼段中的任何註釋將影響執行的結果。

3.2文件註釋

規則3.2-1(強制):文件註釋需放到文件開頭,具體格式見實例。

實例:

stm32f10x_dac.h

  /**

******************************************************************************

  * @file     stm32f10x_dac.h

* @brief    Thisfile contains all the functions prototypes for the DAC firmware

  *          library.

  * @author  MCD Application Team

  * @version  V3.5.0

  * @date    11-March-2014

* @par     Modification:添加函數,支持********<br>

*                     History

*          Version:V3.0.1 <br>

*          Author:***<br>

*          Modification:添加函數,支持********<br>

*          Version:V3.0.0 <br>

*          Author:***<br>

*          Modification:添加函數,支持********<br>

  *************************************************************************

  * @attention

*********************************************************

*/

說明:註釋格式可被doxygen工具識別,其中@file、@brief、@author等是doxygen工具識別的關鍵字,註釋內容能夠爲中文。

3.3函數註釋

規則3.3-1(強制):函數註釋分爲頭文件中函數原型聲明時的註釋和源文件中函數實現時的註釋。頭文件中的註釋注重函數使用方法和注意事項,源文件中的註釋注重函數實現原理和方法。具體格式見實例。

說明:函數原型聲明的註釋按照doxygen工具能夠識別的格式進行註釋,用於doxygen工具

生成頭文件信息以及函數間的調用關係信息。源代碼實現主要是註釋函數實現原理及

修改記錄,不需按照doxygen工具要求的註釋格式進行註釋。

實例:

  頭文件函數原型聲明註釋:

/**

********************************************************************

* @brief     Configures the discontinuous mode for theselected ADC regular

*           group channel.

* @param    ADCx:where x can be 1, 2 or 3 to select the ADC peripheral.

* @param    Number:specifies the discontinuous mode regular channel

*           count value. This number must be between 1 and8.

* @retval    None

* @par      Usage:

*           ADC_DiscModeChannelCountConfig(ADC1,6);<br>

* @par      Tag:

*           此函數不能在中斷裏調用。

********************************************************************

*/

void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, INT8U_tNumber);

 

源文件函數實現註釋:

/*

********************************************************************

  * @brief    Configures the discontinuousmode for the selected ADC regular

  *          group channel.

  * @param   ADCx: where x can be 1, 2 or 3 toselect the ADC peripheral.

  * @param   Number: specifies the discontinuousmode regular channel

  *          count value. This number must bebetween 1 and 8.

  * @retval   None

  * @par     Modification:修改了********<br>

*                       History

  *          Modified by:***<br>

*           Date: 2013-10-10

  *          Modification:修改了********<br>

********************************************************************

  */

void ADC_DiscModeChannelCountConfig(ADC_TypeDef*ADCx, INT8U_t Number)

{

    賦值語句*********;       /*關鍵語句的註釋           */

語句***********;         /*關鍵語句的註釋格式       */

語句*******;             /*實現*****************功能*/

}

 

3.4常量及全局變量註釋

規則3.3-1(強制):常量、全局變量須要註釋,註釋格式見實例。

實例:

/** Description of the macro */

#define XXXX_XXX_XX           0

 

/**Description of global variable */

INT8U  G_xxx  =  0;

說明:若全局變量在.c文件中定義,又在.h文件中聲明,則在頭文件中使用doxygen

格式註釋,在源碼文件中使用  /* Description of the globalvariable */的形式。防止doxygen生成兩遍註釋文檔信息。

 

3.5局部變量及語句註釋

規則3.3-1(強制):局部變量,函數實現關鍵語句須要註釋,註釋格式見實例。

實例:

      *pq->OSQIn++ = pmsg;              /* Insert message into queue          */

       pq->OSQEntries++;                /* Update the nbr of entries in the queue*/

      if (pq->OSQIn== pq->OSQEnd)

          {          

pq->OSQIn = pq->OSQStart;    /* Wrap IN ptr if we are at end of queue */

      }

說明:局部變量,關鍵語句須要註釋,從功能和意圖上進行註釋,而不是代碼的重複。多條註釋語句儘可能保持對齊,實現美觀,整潔。

材料:

1.        《代碼整潔之道》(RobertC.Martin 著 韓磊 譯 人民郵電出版社2010年1月)第四章"註釋」。

2.        《Doxygen中文手冊》

4項目版本號命名規範

項目版本號管理是項目管理的重要方面,咱們根據項目不一樣的開發階段制定了不一樣的版本號命名規範。項目開發過程通常分爲前期開發測試階段、發佈階段、維護階段這三個主要階段,咱們分別制定了命名規範。

4.1開發、測試階段版本號命名

規則4.1-1(強制):處於開發、調試階段的項目,版本號使用「V0.yz」的形式。

說明:處於新開發、調試階段的項目,版本號使用「V0.yz」 的形式,好比新開發的項目正處在開發、調試階段,這時可使用「 V0.10 」這樣的版本號。你認爲完成了新的功能模塊或總體架構作了很大的修改,能夠根據狀況增長 Y 或者 Z的值。好比,你開發階段在「 V0.10 」基礎上新增長了一個功能模塊你能夠將版本號改成「V0.11」,作了比較大的修改,你能夠將版本號定爲「V0.20」。

4.2正式發佈階段版本號命名

規則4.2-1(強制):處於正式發佈階段的項目,版本號使用「Vx.y」的形式。

說明:處於正式發佈的項目版本號使用「Vx.y」的形式。好比,你發佈了一個正式面向市場的項目,你可使用「V1.0」做爲正式的版本號。在「V1.0」基礎上增長功能的正式版本,你可使用「V1.1」做爲下一次正式版本的版本號,在「V1.0」基礎上修正了大的BUG或者作了很大的改動,你可使用「V2.0」做爲下一次正式版本號。

4.3維護階段版本號命名

規則4.3-1(強制):處於維護階段的項目,版本號使用「Vx.yz」的形式。

說明:處於維護階段的項目版本號使用「Vx.yz」的形式。好比在"V1.1"的基礎上修改了一個功能實現算法以實現高效率,則可使用"V1.11" 來表示這是在正式發佈版本「V1.1」的基礎上進行的一次修正,再次修正可使用「V1.12」。

 

5嵌入式軟件安全性相關規範

5.1頭文件

原則5.1-1(強制):頭文件用於聲明模塊對外接口,包括具備外部連接的函數原型聲明、全局變量聲明、定義的類型聲明等。

 

說明:頭文件是模塊(Module)或單元(Unit)的對外接口。頭文件中應放置對外部的聲

明,如對外提供的函數聲明、宏定義、類型定義等。內部使用的函數聲明不該放在

頭文件中。 內部使用的宏、枚舉、結構定義不該放入頭文件中。變量定義不該放

在頭文件中,應放在.c文件中。 變量的聲明儘可能不要放在頭文件中,亦即儘可能不要

使用全局變量做爲接口。變量是模塊或單元的內部實現細節,不該經過在頭文件中

聲明的方式直接暴露給外部,應經過函數接口的方式進行對外暴露。即便必須使用

全局變量,也只應當在.c中定義全局變量,在.h中僅聲明變量爲全局的。

材料:《C語言接口與實現》(David R. Hanson著 傅蓉 周鵬 張昆琪權威 譯 機械工業出

版社 2004年1月)(英文版: "C Interfaces and Implementations")

規則5.1-2(強制):只能經過包含頭文件的方式使用其餘.c提供的接口,禁止在.c中經過extern的方式使用外部函數接口、變量。

說明:若a.c使用了b.c定義的foo()函數 ,則應當在b.h中聲明externint foo(int input);並

在a.c中經過#include <b.h>來使用foo。禁止經過在a.c中寫externint foo(int input);

來使用foo,後面這種寫法容易在foo改變時可能致使聲明和定義不一致。

 

規則5.1-3(強制):使用#define定義保護符,防止頭文件重複包含。

說明:屢次包含一個頭文件能夠經過認真的設計來避免。若是不能作到這一點,就須要採起

阻止頭文件內容被包含多於一次的機制。一般的手段是爲每一個文件配置一個宏,當頭

文件第一次被包含時就定義這個宏,並在頭文件被再次包含時使用它以排除文件內

容。全部頭文件都應當使用#define 防止頭文件被多重包含,命名格式FILENAME_H_,

其中FILENAME 爲頭文件的名稱。

實例:

      若文件名爲:stm32f10x_adc.h。

     

#ifndef          STM32F10x_DAC_H_

#define             STM32F10x_DAC_H_

…………

受保護的代碼

#endif

5.2預處理命令

規則5.2-1(強制):C的宏只能擴展爲用大括號括起來的初始化、常量、小括號括起來的

表達式、類型限定符、存儲類標識符或do-while-zero 結構。

說明:這些是宏當中全部可容許使用的形式。存儲類標識符和類型限定符包括諸如extern

staticconst這樣的關鍵字。使用任何其餘形式的#define 均可能致使非預期的行爲,

或者是很是難懂的代碼。特別的,宏不能用於定義語句或部分語句,除了do-while 結

構。宏也不能重定義語言的語法。宏的替換列表中的全部括號,無論哪一種形式的 ()、

{} 、[]  都應該成對出現。 do-while-zero 結構(見下面實例)是在宏語句體中惟一可接受的具備完整語句的形式。do-while-zero 結構用於封裝語句序列並確保其是正確的。

注意:在宏語句體的末尾必須省略分號。

   實例

                   如下是合理的宏定義:

       #define   PI 3.14159F                  /*Constant */

        #define  XSTAL 10000000                /*Constant */

        #define  CLOCK (XSTAL / 16)             /*Constant expression */

        #define   PLUS2(X)  ( (X) + 2 )            /* Macro expanding to expression */

        #define  STOR extern                  /*storage class specifier */

        #define   INIT(value)  { (value), 0, 0 }       /*braced initialiser */

        #define   READ_TIME_32() \

        do  { \

                     DISABLE_INTERRUPTS(); \

                    time_now = (INT32U) TIMER_HI << 16; \

                    time_now = time_now | (INT32U) TIMER_LO; \

                     ENABLE_INTERRUPTS(); \

           } while(0)                           /* example of do-while-zero */

             如下是不合理的宏定義:

                   #define unsigned int  long     /* use typedef instead */

        #defineSTARTIF  if(           /* unbalanced () and languageredefinition */

 

規則5.2-2(強制):在定義函數宏時,每一個參數實例都應該以小括號括起來。

實例:

          一個abs 函數能夠定義成:

     #define  abs (x) ( ( (x) >= 0 ) ? (x) : -(x) )

不能定義成:

    #define   abs(x)  ( ( (x) >= 0 ) ? x : -x )

若是不堅持本規則,那麼當預處理器替代宏進入代碼時,操做符優先順序將不會給出要

求的結果。

考慮前面第二個不正確的定義被替代時會發生什麼:

       z = abs ( a – b );

將給出以下結果:

       z = ( ( a – b >= 0 ) ? a – b : -a – b );

子表達式  – a - b  至關於 (-a)-b ,而不是但願的 –(a-b) 。把全部參數都括進小括號中就能夠避免這樣的問題。

 

 

規則5.2-3(建議):使用宏時,不容許參數數值發生變化。

實例:

         以下用法可能致使錯誤。

#define SQUARE(a) ((a) * (a))

int a = 5;

int b;

b = SQUARE(a++);           /*結果:a = 7,即執行了兩次增。

 

正確的用法是:

b = SQUARE(a);

a++;                                      /*結果:a = 6,即只執行了一次增*/

 

一樣建議在調用函數時,參數也不要變化,若是某次軟件升級將其中一個接口由函數實現轉換成宏,那參數數值發生變化的調用將產生非預期效果。

 

規則5.2-4(建議):除非必要,應儘量使用函數代替宏。

 

說明:宏能提供比函數優越的速度,可是沒有參數檢查機制,不當的使用可能產生非預期後果。

 

5.3類型及類型轉換

規則5.3-1(強制):應該使用標明瞭大小和符號的typedef代替基本數據類型。不該使用基本數值類型char、int、short、long、float和double,而應使用typedef進行類型的定義。

說明:爲了程序的跨平臺移植性,咱們使用typedef定義指明瞭大小和符號的數據類型。

實例:

       此實例是根據keil for ARM的數據類型大小進行的定義。

 

No.

基本數據類型

Typedef定義

1

typedef unsigned char

BOOLEAN

2

typedef unsigned char

INT8U

3

typedef signed   char

INT8S

4

typedef unsigned short

INT16U

5

typedef signed   short

INT16S

6

typedef unsigned int

INT32U

7

typedef signed   int

INT32S

8

typedef float

FP32

9

typedef double

FP64

 

應根據硬件平臺和編譯器的信息對基本類型進行定義。

 

規則5.3-2(建議):浮點應用應該適應於已定義的浮點標準。

說明:浮點運算會帶來許多問題,一些問題(而不是所有)能夠經過適應已定義的標準來克服。其中一個合適的標準是  ANSI/IEEE Std 754 [1] 。

 

5.3.1 顯式數據類型轉換

C 語言給程序員提供了至關大的自由度並容許不一樣數值類型能夠自動轉換。因爲某些功能

性的緣由能夠引入顯式的強制轉換,例如:

1.        用以改變類型使得後續的數值操做能夠進行

2.        用以截取數值

3.        出於清晰的角度,用以執行顯式的類型轉換

爲了代碼清晰的目的而插入的強制轉換一般是有用的,但若是過多使用就會致使程序的

可讀性降低。正以下面所描述的,一些隱式轉換是能夠安全地忽略的,而另外一些則不能。

規則5.3.1-1(強制):強制轉換隻能向表示範圍更窄的方向轉換,且與被轉換對象的類

型具備相同的符號。浮點類型值只能強制轉換到更窄的浮點類型。

說明:這條規則主要是要求須要強制轉換時,須明確被轉換對象的表示範圍及轉換後的表示

範圍。轉換時儘可能保持符號一致,不一樣符號對象之間不該出現強制轉換。向更寬數據

範圍轉換並不能提升數據精確度,並無實際意義。在程序中儘可能規劃好變量範圍,

儘可能少使用強制轉換。

 

規則5.3.1-2(強制):若是位運算符 ~  和 <<  應用在基本類型爲unsigned char或unsigned

short 的操做數,結果應該當即強制轉換爲操做數的基本類型。

說明:當這些操做符(~ 和<<)用在 small integer 類型(unsigned char 或unsigned short )時,運算以前要先進行整數提高,結果可能包含並不是預期的高端數據位。

例如:

         INT8U port= 0x5aU;

        INT8U result_8;

        INT16U result_16;

        INT16U mode;

       result_8 = (~port) >> 4;    /* 不合規範 */

~port的值在16位機器上是 0xffa5 ,而在 32 位機器上是 0xffffffa5 。在每種狀況下,result的值是0xfa ,然而指望值多是0x0a 。這樣的危險能夠經過以下所示的強制轉換來避免:

result_8 = ( (INT8U) (~port  )) >> 4;            /* 符合規範 */

result_16 = ( (INT16U ) (~(INT16U) port ) ) >> 4 ;   /*符合規範  */

當<<操做符用在 smallinteger 類型時會遇到相似的問題,高端數據位被保留下來。

例如:

result_16 = ( ( port << 4 ) & mode ) >> 6 ;         /*不符合規範 */

result_16 的值將依賴於 int 實現的大小。附加的強制轉換能夠避免任何模糊性。

result_16 = ( ( INT16U) ( ( INT16U ) port << 4 ) & mode )>> 6 ;  /*  符合規範 */

5.3.2隱式類型轉換

規則5.3.2-1(強制):如下類型之間不該該存在隱式類型轉換。

1)        有符號和無符號之間沒有隱式轉換

2)        整型和浮點類型之間沒有隱式轉換

3)        沒有從寬類型向窄類型的隱式轉換

4)        函數參數沒有隱式轉換

5)        函數的返回表達式沒有隱式轉換

6)        複雜表達式沒有隱式轉換

5.3.3 整數後綴

規則5.3.3-1(強制):後綴「U」應該用在全部unsigned 類型的常量上。

整型常量的類型是混淆的潛在來源,由於它依賴於許多因素的複雜組合,包括:

1)        常數的量級

2)        整數類型實現的大小

3)        任何後綴的存在

4)        數值表達的進制(即十進制、八進制或十六進制)

例如,整型常量「40000」在32位環境中是 int 類型,而在 16位環境中則是long 類型。

值0x8000 在16位環境中是 unsigned int 類型,而在 32 位環境中則是(signed )int 類型。

注意:

1)        任何帶有「U」後綴的值是unsigned 類型

2)        一個不帶後綴的小於231的十進制值是signed 類型

可是:

1)        不帶後綴的大於或等於215的十六進制數多是 signed 或unsigned 類型

2)        不帶後綴的大於或等於231的十進制數多是 signed 或unsigned 類型

常量的符號應該明確。符號的一致性是構建良好形式的表達式的重要原則。若是一個常

數是unsigned 類型,爲其加上「U」後綴將有助於避免混淆。當用在較大數值上時,後綴也許是多餘的(在某種意義上它不會影響常量的類型);然然後綴的存在對代碼的清晰性是種有價值的幫助。

5.3.4 指針類型轉換

指針類型能夠歸爲以下幾類:

1)        對象指針

2)        函數指針

3)        void 指針

4)        空(null )指針常量(即由數值 0 強制轉換爲 void*類型)

涉及指針類型的轉換須要明確的強制,除非在如下時刻:

1)        轉換髮生在對象指針和void 指針之間,並且目標類型承載了源類型的全部類型標識符。

2)        當空指針常量(void*)被賦值給任何類型的指針或與其作等值比較時,空指針常量被自動轉化爲特定的指針類型。

C 當中只定義了一些特定的指針類型轉換,而一些轉換的行爲是實現定義的。

 

規則5.3.9-1(強制):轉換不能發生在函數指針和其餘除了整型以外的任何類型指針之間。

[Undefined]

 

說明:

函數指針到不一樣類型指針的轉換會致使未定義的行爲。這意味着一個函數指

針不能轉換成指向不一樣類型函數的指針。

規則5.3.9-2(強制):對象指針和其餘除整型以外的任何類型指針之間、對象指針和其餘類

型對象的指針之間、對象指針和void指針之間不能進行轉換。

[Undefined]

規則5.3.9-3(強制):不該在某類型對象指針和其餘不一樣類型對象指針之間進行強制轉換。

說明:若是新的指針類型須要更嚴格的分配時這樣的轉換多是無效的。

實例:

                   INT8U   *p1;

                 INT32U  *p2;

                p2= (INT32U *) p1;  /*不符規範*/

 

5.4初始化、聲明與定義

規則5.4-1(強制):全部自動變量在使用前都應被賦值。

[Undefined]

說明:注意,根據ISO C[2] 標準,具備靜態存儲期的變量缺省地被自動賦予零值,除非通過了顯式的初始化。實際中,一些嵌入式環境沒有實現這樣的缺省行爲。靜態存儲期是全部以static存儲類形式聲明的變量或具備外部連接的變量的共同屬性,自動存儲期變量一般不是自動初始化的。

 

規則5.4-2(強制):應該使用大括號以指示和匹配數組和結構的非零初始化構造。

[Undefined]

 

說明:

              ISO C[2]要求數組、結構和聯合的初始化列表要以一對大括號括起來(儘管不這樣作的行爲是未定義的)。本規則更進一步地要求,使用附加的大括號來指示嵌套的結構。它迫使程序員顯式地考慮和描述複雜數據類型元素(好比,多維數組)的初始化次序。

例如,下面的例子是二維數組初始化的有效(在ISO C [2]中)形式,但第一個與本規

則相違背:

在結構中以及在結構、數組和其餘類型的嵌套組合中,規則相似。

還要注意的是,數組或結構的元素能夠經過只初始化其首元素的方式初始化(爲 0 或

NULL)。若是選擇了這樣的初始化方法,那麼首元素應該被初始化爲0(或NULL),

此時不須要使用嵌套的大括號。

實例:

INT16U  test[3][2] = { 1, 2, 3, 4, 5, 6 };          /* 不符合此規則    */

INT16U  test[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };   /* 符合此規則       */

 

規則5.4-3(強制):在枚舉列表中,「= 」不能顯式用於除首元素以外的元素上,除非全部

的元素都是顯式初始化的。

說明:

若是枚舉列表的成員沒有顯式地初始化,那麼C 將爲其分配一個從0 開始的整數序列,首元素爲0 ,後續元素依次加 1 。

如上規則容許的,首元素的顯式初始化迫使整數的分配從這個給定的值開始。當採用這種方法時,重要的是確保所用初始化值必定要足夠小,這樣列表中的後續值就不會超出該枚舉常量所用的int 存儲量。

列表中全部項目的顯式初始化也是容許的,它防止了易產生錯誤的自動與手動分配的混合。然而,程序員就該擔負職責以保證全部值都處在要求的範圍內以及值不是被無心複製的。

實例:

enum  colour { red = 3, blue, green, yellow = 5 };  /* 不符合此規則 */

 

enum  colour { red = 3, blue = 4, green = 5, yellow= 5 };  /* 符合此規則 */

雖然green和yellow的值都是5,但這符合規則。

 

enum  colour { red = 1, blue, green, yellow };  /* 符合此規則 */

 

規則5.4-4(強制):函數應當具備原型聲明,且原型在函數的定義和調用範圍內都是可見

的。

[Undefined]

說明:原型的使用使得編譯器可以檢查函數定義和調用的完整性。若是沒有原型,就不會迫使編譯器檢查出函數調用當中的必定錯誤(好比,函數體具備不一樣的參數數目,調用和定義之間參數類型的不匹配)。事實證實,函數接口是至關多問題的肇因,所以本規則是至關重要的。對外部函數來講,咱們建議採用以下方法,在頭文件中聲明函數(亦即給出其原型),並在全部須要該函數原型的代碼文件中包含這個頭文件,在實現函數功能的.c文件中也包含具備原型聲明的頭文件。 爲具備內部連接的函數給出其原型也是良好的編程實踐。

 

規則5.4-5(強制):定義或聲明對象、函數時都應該顯示指明其類型。

 

規則5.4-6(強制):函數的每一個參數類型在聲明和定義中必須是等同的,函數的返回類型

也該是等同的。

[Undefined]

規則5.4-6(強制):函數應該聲明爲具備文件做用域。

[Undefined]

說明:在塊做用域中聲明函數會引發混淆並可能致使未定義的行爲。

 

規則5.4-7(強制):在文件範圍內聲明和定義的全部對象或函數應該具備內部連接,除非

是在須要外部連接的狀況下,具備內部連接屬性的對象或函數應該使用static關鍵字修飾。

 

說明:若是一個變量只是被同一文件中的函數所使用,那麼就用static。相似地,若是一個函數只是在同一文件中的其餘地方調用,那麼就用 static。使用 static存儲類標識符將確保標識符只是在聲明它的文件中是可見的,而且避免了和其餘文件或庫中的相同標識符發生混淆的可能性。具備外部連接屬性的對象或函數在相應模塊的頭文件中聲明,在須要使用這些接口的模塊中包含此頭文件。

 

規則5.4-8(強制):當一個數組聲明爲具備外部連接,它的大小應該顯式聲明或者經過初

始化進行隱式定義。

[Undefined]

實例:

                   INT8U array[10] ;               /* 符合規範  */

extern  INT8U  array[] ;         /* 不符合規範*/

INT8U  array[] = { 0, 10, 15};       /* 符合規範   */

 

儘管能夠在數組聲明不完善時訪問其元素,然而仍然是在數組的大小能夠顯式肯定的情

況下,這樣作纔會更爲安全。

5.5控制語句和表達式

規則5.5-1(建議):不要過度依賴C 表達式中的運算符優先規則。

 

說明:括號的使用除了能夠覆蓋缺省的運算符優先級之外,還能夠用來強調所使用的運算符。使用至關複雜的C 運算符優先級規則很容易引發錯誤,那麼這種方法就能夠幫助避免這樣的錯誤,而且可使得代碼更爲清晰可讀。然而,過多的括號會分散代碼使其下降了可讀性所以,請合理使用括號來提升程序清晰度和可讀性。

 

規則5.5-1(強制):不能在具備反作用的表達式中使用sizeof 運算符。

說明:當一個表達式使用了sizeof運算符,並指望計算表達式的值時,表達式是不會被計算的。sizeof只對表達式的類型有用。

實例:

INT32S  i;

             INT32S  j;

           j = sizeof (i = 1234); /* j的值是i類型的大小,但i的值並無賦值成1234 */

 

規則5.5-2(強制):邏輯運算符 && 或 || 的右手操做數不能包含反作用。

說明:C語言中存在表達式的某些部分不會被計算到,這取決於表達式中的其餘部分。邏輯操做符&&或||在進行邏輯判斷時,若僅判別左操做數就能肯定true or false的狀況下,邏輯操做符的右操數將被忽略。

實例:

if ( high && ( x == i++ ) )    /* 不符合規則 */

 

           若high爲false,則整個表達式的布爾值也即爲false,不用再去執行和判斷右操做數。

 

規則5.5-3(建議):邏輯運算符(&&、| |  和 ! )的操做數應該是有效的布爾數。有效布爾

類型的表達式不能用作非邏輯運算符(&&、| |  和 ! )的操做數。

 

說明:有效布爾類型是表示真、假的一種數據類型,產生布爾類型的能夠是比較,邏輯運算,但布爾類型數據只能進行邏輯運算。

 

規則5.5-4(強制):位運算符不能用於基本類型(underlying type )是有符號的操做數上。

[Implementation-defined]

 

說明:位運算(~ 、<<、>>、&、^  和 |  )對有符號整數一般是無心義的。好比,若是右移運算把符號位移動到數據位上或者左移運算把數據位移動到符號位上,就會產生問題。

 

規則5.5-6(建議):在一個表達式中,自增(++)和自減(- - )運算符不該同其餘運算符

混合在一塊兒。

說明:不建議使用同其餘算術運算符混合在一塊兒的自增和自減運算符是由於

1)它顯著削弱了代碼的可讀性;

2)在不一樣的變異環境下,會執行不一樣的運算次序,產生不一樣結果。

實例:

  u8a = ++u8b +u8c--;  /* 不符合規範 */

下面的序列更爲清晰和安全:

                 ++u8b;

                u8a = u8b + u8c;

                 u8c--;

 

規則5.5-7(強制):浮點表達式不能作像‘>’ ‘<’ ‘==’ ‘!=’等 關係運算。

說明:float、double類型的數據都有必定的精確度限制,使用不一樣浮點數表示規範或者不一樣硬件平臺可能致使關係運算的結果不一致。

 

規則5.5-8(強制):for語句的三個表達式應該只關注循環控制,for循環中用於計數的變量不該在循環體中修改。

說明:for 語句的三個表達式都給出時它們應該只用於以下目的:

第一個表達式初始化循環計數器;

第二個表達式包含對循環計數器和其餘可選的循環控制變量的測試;

第三個表達式循環計數器的遞增或遞減。

 

規則5.5-9(強制):組成switch、while、do...while 或for 結構體的語句應該是複合語句。即便該複合語句只包含一條語句也要擴在{}裏。

實例:

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

    {

         buffer[i] = 0;    /* 僅有一條語句也需使用{} */

    }

規則5.5-10(強制):if /else應該成對出現。全部的if ... else if 結構應該由else 子句結束。

 

規則5.5-11(強制):switch 語句中若是case 分支的內容不爲空,那麼必須以break 做爲結束,最後分支應該是default分支。

 

 

5.6函數

原則5.6-1(強制):編寫整潔函數,同時把代碼有效組織起來。

 

說明:代碼簡單直接、不隱藏設計者的意圖、用乾淨利落的抽象和直截了當的控制語句將函數有機組織起來。代碼的有效組織包括:邏輯層組織和物理層組織兩個方面。邏輯層,主要是把不一樣功能的函數經過某種聯繫組織起來,主要關注模塊間的接口,也就是模塊的架構。物理層,不管使用什麼樣的目錄或者名字空間等,須要把函數用一種標準的方法組織起來。例如:設計良好的目錄結構、函數名字、文件組織等,這樣能夠方便查找。

規則5.6-2(強制):必定要顯示聲明函數的返回值類型,及所帶的參數。若是沒有要聲明爲void。

說明:C語言中不加類型說明的函數,一概自動按整型處理。

 

規則5.6-3(建議):不建議使用遞歸函數調用。

 

說明:有些算法使用分而治之的遞歸思想,但在嵌入式中棧空間有限,遞歸自己承載着可

用堆棧空間過分的危險,這能致使嚴重的錯誤。除非遞歸通過了很是嚴格的控制,

不然不可能在執行以前肯定什麼是最壞狀況(worst-case)的堆棧使用。

5.7指針與數組

規則5.7-1(強制):除了指向同一數組的指針外,不能用指針進行數學運算,不能進行關係運算。

說明:這樣作的目的一是使代碼清晰易讀,另外避免訪問無效的內存地址。

 

規則5.7-2(強制):指針在使用前必定要賦值,避免產生野指針。

 

規則5.7-3(強制):不要返回局部變量的地址。

說明:局部變量是在棧中分配的,函數返回後佔用的內存會釋放,繼續使用這樣的內存是

危險的。所以,應該避免出現這樣的危險。

實例:

           INT8U *foobar (void)

          {

                  INT8U local_auto;

                 return(&local_auto);     /* 不符合規範 */

      }

5.8結構與聯合

原則5.8-1(強制):結構功能單一,不要設計面面俱到的數據結構。

 

說明:相關的一組信息纔是構成一個結構體的基礎,結構的定義應該能夠明確的描述一個對象,而不是一組相關性不強的數據的集合。設計結構時應力爭使結構表明一種現實事務的抽象,而不是同時表明多種。結構中的各元素應表明同一事務的不一樣側面,而不該把描述沒有關係或關係很弱的不一樣事務的元素放到同一結構中。

 

5.9標準庫

規則5.9-1(強制):標準庫中保留的標識符、宏和函數不能被定義、重定義或取消定義。

[Undefined]

 

說明:一般 #undef  一個定義在標準庫中的宏是件壞事。一樣很差的是,#define 一個宏名字,而該名字是C 的保留標識符或者標準庫中作爲宏、對象或函數名字的C 關鍵字。例如,存在一些特殊的保留字和函數名字,它們的做用爲人所熟知,若是對它們從新定義或取消定義就會產生一些未定義的行爲。這些名字包括defined、__LINE__、__FILE__、__DATE__ 、__TIME__、__STDC__、errno和assert。

 

規則5.9-2(強制):傳遞給庫函數的值必須檢查其有效性。

 

說明:C 標準庫中的許多函數根據ISO [2] 標準 並不須要檢查傳遞給它們的參數的有效性。即便標準要求這樣,或者編譯器的編寫者聲明要這麼作,也不能保證會作出充分的檢查。所以,程序員應該爲全部帶有嚴格輸入域的庫函數(標準庫、第三方庫及本身定義的庫)提供適當的輸入值檢查機制。

具備嚴格輸入域並須要檢查的函數例子爲:

math.h 中的許多數學函數,好比:

負數不能傳遞給sqrt 或log函數;

fmod 函數的第二個參數不能爲零

  toupper 和tolower:當傳遞給toupper函數的參數不是小寫字符時,某些實現能產生並不是預期的結果(tolower 函數狀況相似)

     若是爲ctype.h 中的字符測試函數傳遞無效的值時會給出未定義的行爲

     應用於大多數負整數的abs 函數給出未定義的行爲 在math.h 中,儘管大多數數學庫函數定義了它們容許的輸入域,但在域發生錯誤時它們的返回值仍可能隨編譯器的不一樣而不一樣。所以,對這些函數來講,預先檢查其輸入值的有效性就變得相當重要。

程序員在使用函數時,應該識別應用於這些函數之上的任何的域限制(這些限制可能

會也可能不會在文檔中說明),而且要提供適當的檢查以確認這些輸入值位於各自域

中。固然,在須要時,這些值還能夠更進一步加以限制。

有許多方法能夠知足本規則的要求,包括:

1.  調用函數前檢查輸入值

2.  設計深刻函數內部的檢查手段。這種方法尤爲適應於實驗室內開發的庫,縱然它也能夠用於買進的第三方庫(若是第三方庫的供應商聲明他們已內置了檢查的話)。

3.  產生函數的「封裝」(wrapped)版本,在該版本中首先檢查輸入,而後調用原始的函數。

4.  靜態地聲明輸入參數永遠不會採起無效的值。

注意,在檢查函數的浮點參數時(浮點參數在零點上爲奇點),適當的作法是執行其是否爲零的檢查。然而若是當參數趨近於零時,函數值的量級趨近無窮的話,仍然有必要檢查其在零點(或其餘任何奇點)上的容限,這樣能夠避免溢出的發生。

6參考文獻

[1]  ANSI/IEEE Std 754, IEEE Standard for Binary Floating-Point Arithmetic,1985

[2]   ISO/IEC 9899:1990. Programming languages - C.International Organization for

Standardization. 1990

[3]  GB/T 15272-94 程序設計語言 C

[4]  MISRA-C-:2004 Guidelines for the use of the C language in critical systems


================================================================

word版下載

相關文章
相關標籤/搜索