嵌入式c編程技巧_編程風格

 
目錄:
.編程修養
.編程技巧
.編程風格
 
/*******************************************************
.編程修養
 ----C語言程序寫作上的三十二個“修養”
*******************************************************/
 
    ————————————————————————
 
        01、版權和版本
        02、縮進、空格、換行、空行、對齊
        03、程序註釋
        04、函數的[in][out]參數
        05、對系統調用的返回進行判斷
        06if 語句對出錯的處理
        07、頭文件中的#ifndef
        08、在堆上分配內存
        09、變量的初始化
        10h和c文件的使用
        11、出錯信息的處理
        12、常用函數和循環語句中的被計算量
        13、函數名和變量名的命名
        14、函數的傳值和傳指針
        15、修改別人程序的修養
        16、把相同或近乎相同的代碼形成函數和宏
        17、表達式中的括號
        18、函數參數中的const
        19、函數的參數個數
        20、函數的返回類型,不要省略
        21goto語句的使用
        22、宏的使用
        23static的使用
        24、函數中的代碼尺寸
        25typedef的使用
        26、爲常量聲明宏
        27、不要爲宏定義加分號
        28||和&&的語句執行順序
        29、儘量用for而不是while做循環
        30、請sizeof類型而不是變量
        31、不要忽略Warning
        32、書寫Debug版和Release版的程序
        21goto語究使勁
        22、宏的使用
        23static的使用
        24、函數中的代碼尺寸
        25typedef的使用
        26、爲常量聲明宏
        27、不要爲宏定義加分號
        28||和&&的語句執行順序
        29、儘量用for而不是while做循環
        30、請sizeof類型而不是變量
        31、不要忽略Warning
        32、書寫Debug版和Release版的程序
 
    ————————————————————————
1.版權和版本
file: in top of file as
/************************************************************************
*
*   filenamefilename.c
*
*   description: ...
*
*   author sharp_xiao, (time)2007-04-05
*
*   version number1.0
*
*   amend notes:
*
*
************************************************************************/
 
function: in front function as
/*================================================================
*
* name:XXX
*
* input:
*        type name [IN] : descripts
 
* output:
*   type name [OUT] : descripts
*
* description: 
*        ..............
*
* return:
*       succed:TRUE, failed: FALSE
*
* pop abnormity:
*
* author: sharp_xiao 2007-04-05
*
*
================================================================*/
 
2. 函數指針
首先要理解以下三個問題:
 (1)C語言中函數名直接對應於函數生成的指令代碼在內存中的地址,因此函數名可以直接賦給指向函數的指針;
 (2)調用函數實際上等同於"調轉指令+參數傳遞處理+迴歸位置入棧",本質上最核心的操作是將函數生成的目標代碼的首地址賦給CPU的PC寄存器;
 (3)因爲函數調用的本質是跳轉到某一個地址單元的code去執行,所以可以"調用"一個根本就不存在的函數實體;
eg.
  typedef void (*lpFunction) ( ); /* 定義一個無參數、無返回類型的 */
  /* 函數指針類型 */
  lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定義一個函數指針,指向*/
  /* CPU啓動後所執行第一條指令的位置 */
  lpReset(); /* 調用函數 */
 
3.數組vs.動態申請
 (1)儘可能的選用數組,數組不能越界訪問(真理越過一步就是謬誤,數組越過界限就光榮地成全了一個混亂的嵌入式系統);
 (2)如果使用動態申請,則申請後一定要判斷是否申請成功了,並且malloc和free應成對出現!即"誰申請,就由誰釋放"原則。不滿足這個原則,會導致代碼的耦合度增大;
eg.
Change:
    char * function(void)
  {
   char *p;
   p = (char *)malloc(…);
   if(p==NULL)
   …;
   … /* 一系列針對p的操作 */
   return p;
  }
  ...
  char *q = function();
  …
  free(q);
To:
  char *p=malloc(…);
  if(p==NULL)
  …;
  function(p);
  …
  free(p);
  p=NULL;
  
  
  void function(char *p)
  {
  … /* 一系列針對p的操作 */
  }
 
4.關鍵字const
 (1)關鍵字const的作用是爲給讀你代碼的人傳達非常有用的信息。例如,在函數的形參前添加const關鍵字意味着這個參數在函數體內不會被修改,屬於"輸入參數"。在有多個形參的時候,函數的調用者可以憑藉參數前是否有const關鍵字,清晰的辨別哪些是輸入參數,哪些是可能的輸出參數。
 (2)合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改,這樣可以減少bug的出現。
 (3)const在C++語言中則包含了更豐富的含義,而在C語言中僅意味着:"只能讀的普通變量",可以稱其爲"不能改變的變量"(這個說法似乎很拗口,但卻最準確的表達了C語言中const的本質),在編譯階段需要的常數仍然只能以#define宏定義!故在C語言中如下程序是非法的:  
  const int SIZE = 10;
  char a[SIZE]; /* 非法:編譯階段不能用到變量/只能用常量定義數組長度 */
 (4)In C++
class A
{
 const int SIZE = 100; // 錯誤,企圖在類聲明中初始化const 數據成員
 int array[SIZE]; // 錯誤,未知的SIZE
};
const 數據成員的初始化只能在類構造函數的初始化表中進行,例如
class A
{
 A(int size); // 構造函數
 const int SIZE ;
};
A::A(int size) : SIZE(size) // 構造函數的初始化表
{
 
}
A a(100); // 對象 a 的SIZE 值爲100
A b(200); // 對象 b 的SIZE 值爲200
 
5.關鍵字volatile
 在變量的定義前加上volatile關鍵字可以防止編譯器進行優化.
 volatile變量可能用於如下幾種情況:
 (1) 並行設備的硬件寄存器(如:狀態寄存器,例中的代碼屬於此類);
 (2) 一箇中斷服務子程序中會訪問到的非自動變量(也就是全局變量);
 (3) 多線程應用中被幾個任務共享的變量。
eg.
Optimize code:
  int a,b,c;
  a = inWord(0x100); /*讀取I/O空間0x100端口的內容存入a變量*/
  b = a;
  a = inWord (0x100); /*再次讀取I/O空間0x100端口的內容存入a變量*/
  c = a;
As:  
  int a,b,c;
  a = inWord(0x100); /*讀取I/O空間0x100端口的內容存入a變量*/
  b = a;
  c = a;
 
6.書寫Debug版和Release版的程序
    #ifdef DEBUG
        void TRACE(char* fmt, ...)
        {
            ......
        }
    #else
        #define TRACE(char* fmt, ...)
    #endif
 
7.儘量用for而不是while做循環
Change:
    p = pHead;
    p = pHead;
 
    while ( p )
    {
        ...
        p = p->next;
    }
To:
   for ( p=pHead; p; p=p->next )
   {
     ..
   }
 
8.爲常量聲明宏
Change:
    for ( i=0; i<120; i++)
    {
        ....
    }
To:
    #define MAX_USR_CNT 120
 
    for ( i=0; i<MAX_USER_CNT; i++)
    {
        ....
    }
eg.int user[120]; use macro as the same
 
9.typedef的使用
 一般來說,一個C的工程中一定要做一些這方面的工作,因爲你會涉及到跨平臺,不同的平
臺會有不同的字長,所以利用預編譯和typedef可以讓你最有效的維護你的代碼,如下所示:  
    #ifdef SOLARIS2_5
      typedef boolean_t     BOOL_T;
    #else
    #else
      typedef int           BOOL_T;
    #endif
 
    typedef short           INT16_T;
    typedef unsigned short UINT16_T;
    typedef int             INT32_T;
    typedef unsigned int    UINT32_T;
 
    #ifdef WIN32
      typedef _int64        INT64_T;
    #else
      typedef long long     INT64_T;
    #endif
 
    typedef float           FLOAT32_T;
    typedef char*           STRING_T;
    typedef unsigned char   BYTE_T;
    typedef time_t          TIME_T;
    typedef INT32_T         PID_T;  
 使用typedef的其它規範是,在結構和函數指針時,也最好用typedef,這也有利於程序的
易讀和可維護性.
 
10.函數中的代碼尺寸
 一個函數完成一個具體的功能,一般來說,一個函數中的代碼最好不要超過600行左右,越
少越好,最好的函數一般在100行以內,300行左右的孫函數就差不多了.
 
11.函數的參數個數(多了請用結構)
 一般來說6個左右.函數參數是從右至左以棧來傳遞的.
 
12、函數名和變量名的命名
————————————
我看到許多程序對變量名和函數名的取名很草率,特別是變量名,什麼a,b,c,aa,bb,cc,
還有什麼flag1,flag2, cnt1, cnt2,這同樣是一種沒有“修養”的行爲。即便加上好的注
釋。好的變量名或是函數名,我認爲應該有以下的規則:
 
    1) 直觀並且可以拼讀,可望文知意,不必“解碼”。
    2) 名字的長度應該即要最短的長度,也要能最大限度的表達其含義。
 
    3) 不要全部大寫,也不要全部小寫,應該大小寫都有,如:GetLocalHostName 或是
UserAccount。
    4) 可以簡寫,但簡寫得要讓人明白,如:ErrorCode -> ErrCode,
ServerListener -> ServLisner,UserAccount -> UsrAcct 等。
    5) 爲了避免全局函數和變量名字衝突,可以加上一些前綴,一般以模塊簡稱做爲前綴
    6) 全局變量統一加一個前綴或是後綴,讓人一看到這個變量就知道是全局的。
    7) 用匈牙利命名法命名函數參數,局部變量。但還是要堅持“望文生意”的原則。
    8) 與標準庫(如:STL)或開發庫(如:MFC)的命名風格保持一致。
 
13.
———————
版權和版本
———————  
對於C/C++的文件,文件頭應該有類似這樣的註釋:
/************************************************************************
*
*   文件名:network.c
*
*   文件描述:網絡通訊函數集
*
*   創建人: Hao Chen, 2003年2月3
*
*   版本號:1.0
*
*   修改記錄:
*
*
************************************************************************/
 
而對於函數來說,應該也有類似於這樣的註釋:
 
/*================================================================
*
* 函 數 名:XXX
*
* 參    數:
*
*        type name [IN] : descripts
*
* 功能描述:
*
*       ..............
*
* 返 回 值:成功TRUE,失敗FALSE
*
* 拋出異常:
*
* 作    者:ChenHao 2003/4/2
*
*
================================================================*/
 
 
 
 
 
 
 
 
/*******************************************************
.編程技巧
 ---(C語言嵌入式系統編程修煉)
*******************************************************/
1).數據指針
unsigned char *p = (unsigned char *)0xF000FF00;
*p=11;
 
2).函數指針
  首先要理解以下三個問題:
  a.C語言中函數名直接對應於函數生成的指令代碼在內存中的地址,因此函數名可以直接賦給指向函數的指針;
  b.調用函數實際上等同於"調轉指令+參數傳遞處理+迴歸位置入棧",本質上最核心的操作是將函數生成的目標代碼的首地址賦給CPU的PC寄存器;
    c.因爲函數調用的本質是跳轉到某一個地址單元的code去執行,所以可以"調用"一個根本就不存在的函數實體.
//"軟重啓"的作用
186 CPU啓動後跳轉至絕對地址0xFFFF0(對應C語言指針是0xF000FFF0,0xF000爲段地址,0xFFF0爲段內偏移)執行,請看下面的代碼:
typedef void (*lpFunction) ( ); /* 定義一個無參數、無返回類型的 */
/* 函數指針類型 */
lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定義一個函數指針,指向*/
/* CPU啓動後所執行第一條指令的位置 */
lpReset(); /* 調用函數 */
 
3)數組vs.動態申請
a.儘可能的選用數組,數組不能越界訪問;
b.如果使用動態申請,則申請後一定要判斷是否申請成功了,並且malloc和free應成對出現!
 
4)關鍵字const
稱其爲"不能改變的變量",在編譯階段需要的常數仍然只能以#define宏定義!
:const int SIZE = 10;
char a[SIZE]; /* 非法:編譯階段不能用到變量 */
 
5)關鍵字volatile
volatile變量可能用於如下幾種情況:
  a.並行設備的硬件寄存器(如:狀態寄存器,例中的代碼屬於此類);
  b.一箇中斷服務子程序中會訪問到的非自動變量(也就是全局變量);
  c.多線程應用中被幾個任務共享的變量。
 
6)使用宏定義
對於宏,我們需要知道三點: 
a.宏定義“像”函數; 
b.宏定義不是函數,因而需要括上所有“參數”;
c.宏定義可能產生副作用。
eg1.#define MIN(A,B) ((A)<= (B) ? (A) : (B) ) //正確
eg2.least = MIN(*p++, b); 
==>( (*p++) <= (b) ?(*p++):(b) ) //發生的事情無法預料
 
7)利用硬件特性
CPU對各種存儲器的訪問速度,基本上是: 
      CPU內部RAM > 外部同步RAM > 外部異步RAM > FLASH/ROM
 
8)活用位操作
 
9)浮點變量與零值比較
不可將浮點變量用“==”或“!=”與任何數字比較。
eg.   if (x == 0.0) // 隱含錯誤的比較
應轉化爲:
 if ((x>=-EPSINON) && (x<=EPSINON)) //其中EPSINON 是允許的誤差(即精度)
 
10) do not use sizeof(pointer). Because sizeof a pointer is just 4.
eg:
{
   char *ch;
   memset(ch, 0, sizeof(ch)); //there will make some crash issue
}
 
11)notice memset or initialize the variable when define.
 
12)指向類型T的指針並不等價於類型T的數組.
eg. 在一個源文件裏定義了一個數組:
 char a[6];
     則在另外一個文件裏進行extern聲明時不能用:
 extern char *a;
     而只能是: extern char a[];
在使用extern時候要嚴格對應聲明時的格式.
 
 
--------------------------------------
總結
在性能優化方面永遠注意80-20準備,不要優化程序中開銷不大的那80%,這是勞而無功的。
--------------------------------------
 
 
 
/*******************************************************
.編程風格
 ---C語言
*******************************************************/
1.參考"編程修養"
2.命名規則
1)函數->[C++: 類名]用大寫字母開頭的單詞組合而成.
2)局部變量和參數 ->用小寫字母開頭的單詞組合而成.
3)全局變量 ->則使全局變量加前綴g_(表示global).eg.int g_howManyPeople; // 全局變量
 爲了避免全局函數和變量名字衝突,可以加上一些前綴,一般以模塊簡稱做爲前綴.
4)表態變量 ->靜態變量加前綴s_(表示static)。eg. static int s_initValue; // 靜態變量
5)const常量 ->常量全用大寫的字母,用下劃線分割單詞。eg. const int MAX_LENGTH = 100;
6)宏常量 ->一般用大寫字母;但可以瞭解爲函數功能時,可以函數的命名規則命名.
7)數據成員 ->[C++]類的數據成員加前綴m_(表示member),這樣可以避免數據成員與成員函數的參數同名.
eg.    void Object::SetValue(int width, int height)
 {
 m_width = width;
 m_height = height;
 }
8)標識符應當直觀且可以拼讀,可望文知意,不必進行“解碼”.標識符最好採用英文單詞或其組合,便於記憶和閱讀.
9)標識符的長度應當符合“min-length && max-information”原則.
10)命名規則儘量與所採用的操作系統或開發工具的風格保持一致.
11)程序中不要出現僅靠大小寫區分的相似的標識符。eg. int x, X, z, Z;
12)程序中不要出現標識符完全相同的局部變量和全局變量,儘管兩者的作用域不同而不會發生語法錯誤,但會使人誤解.
13)變量的名字應當使用“名詞”或者“形容詞+名詞”。eg. { int oldValue, newValue;}
14)全局函數的名字應當使用“動詞”或者“動詞+名詞”(動賓詞組).
[C++: 類的成員函數應當只使用“動詞”,被省略掉的名詞就是對象本身]. eg. DrawBox(); // 全局函數
15)用正確的反義詞組命名具有互斥意義的變量或相反動作的函數等.
16)儘量避免名字中出現數字編號,如Value1,Value2 等,除非邏輯上的確需要編號。
17)爲了防止某一軟件庫中的一些標識符和其它軟件庫中的衝突,可以爲各種標識符加上能反映軟件性質的前綴。例如三維圖形標準OpenGL 的所有庫函數均以gl 開頭,所有常量(或宏定義)均以GL 開頭.
3.
1) if(NULL == XXX) //use value == XXX to avoid XXX = value
   {
 NULL;   //use NULL when do nothing
   }
 
153102701
421126198904103817
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章