嵌入式c編程技巧_編程風格
目錄:
一.編程修養
二.編程技巧
三.編程風格
/*******************************************************
一.編程修養
----C語言程序寫作上的三十二個“修養”
*******************************************************/
————————————————————————
01、版權和版本
02、縮進、空格、換行、空行、對齊
03、程序註釋
04、函數的[in][out]參數
05、對系統調用的返回進行判斷
06、if 語句對出錯的處理
07、頭文件中的#ifndef
08、在堆上分配內存
09、變量的初始化
10、h和c文件的使用
11、出錯信息的處理
12、常用函數和循環語句中的被計算量
13、函數名和變量名的命名
14、函數的傳值和傳指針
15、修改別人程序的修養
16、把相同或近乎相同的代碼形成函數和宏
17、表達式中的括號
18、函數參數中的const
19、函數的參數個數
20、函數的返回類型,不要省略
21、goto語句的使用
22、宏的使用
23、static的使用
24、函數中的代碼尺寸
25、typedef的使用
26、爲常量聲明宏
27、不要爲宏定義加分號
28、||和&&的語句執行順序
29、儘量用for而不是while做循環
30、請sizeof類型而不是變量
31、不要忽略Warning
32、書寫Debug版和Release版的程序
21、goto語究使勁
22、宏的使用
23、static的使用
24、函數中的代碼尺寸
25、typedef的使用
26、爲常量聲明宏
27、不要爲宏定義加分號
28、||和&&的語句執行順序
29、儘量用for而不是while做循環
30、請sizeof類型而不是變量
31、不要忽略Warning
32、書寫Debug版和Release版的程序
————————————————————————
1.版權和版本
file: in top of file as
/************************************************************************
*
* filename:filename.c
*
* description: ...
*
* author: sharp_xiao, (time)2007-04-05
*
* version number:1.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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.