嵌入式領域,你要了解你的編譯器
我做嵌入式行業,程式設計也多和硬體打交道,好多人說編譯器只是工具,重要的在於演算法和思想。這話說的本來沒錯,但要有一個條件在先:那就是你真正掌握了你所用的編譯器。但就我來看,真正熟悉編譯器的卻並不多見。當你深入瞭解一個編譯器後,你能像用匯編一樣用C,可以像彙編那樣隨心所欲的操作MCU!
瞭解一個編譯器,首先應該有彙編的基礎,不要求能用匯編編寫程式或做過專案,但至少看的懂!不熟悉彙編的嵌入式程式設計師是不合格的程式設計師!
瞭解一個編譯器,最好的方法是看它自帶的幫助檔案,至少要看過Compiler User's Guide ,至少遇到問題會想到到幫助中查詢方法,雖然幫助大多是E文。
1. 在所有的內部和外部識別符號中,大寫和小寫字元不同。
2. 預設情況下,char 型別的資料項是無符號的。它們可以顯式地宣告為signed char 或 unsigned char。
3.基本資料型別的大小和對齊:
型別 | 位大小 | 按位元組自然對齊 |
char | 8 | 1 |
short | 16 | 2 |
int | 32 | 4 |
long | 32 | 4 |
long long | 64 | 8 |
float | 32 | 4 |
double | 64 | 8 |
long double | 64 | 8 |
所有指標 | 32 | 4 |
bool (僅用於C++ ) | 8 | 1 |
_Bool (僅用於C ) | 8 | 1 |
wchar_t (僅用於C++ ) | 16 | 2 |
注:a. 通常區域性變數保留在暫存器中,但當局部變數太多放到棧裡的時候,它們總是字對齊的。例如區域性char
b. 壓縮型別的自然對齊方式為1。使用關鍵字__packed來壓縮特定結構,將所有有效型別的對齊邊界設定為1.
4. 整數以二進位制補碼形式表示;浮點量按IEEE格式儲存。
5. 有符號量的右移是算術移位,即移位時要保證符號位不改變。
6. 對於int類的值:超過31位的左移結果為零;無符號值或正的有符號值超過31位的右移結果為零。負的有符號值移位結果為-1。
7. 整數除法的餘數的符號於被除數相同,由ISO C90標準得出;
8. 如果整型值被截斷為短的有符號整型,則通過放棄適當數目的最高有效位來得到結果。如果原始數是太大的正或負數,對於新的型別 ,無法保證結果的符號將於原始數相同。所以強制型別轉化的時候,對轉換的結果一定要清晰。
9. 整型數超界不引發異常;像unsigned char test; test=1000;這類是不會報錯的,賦值或計算時務必小心。
10. 預設情況下,整型數除以零返回零。
11. 對於兩個指向相同型別和對齊屬性的指標相減,計算結果如下表達式所示:
((int)a ‑ (int)b) / (int)sizeof(指向資料的型別)
12. 在嚴格C中,列舉值必須被表示為整型,例如,必須在‑2147483648 到+2147483647的範圍內。但keil MDK自動使用物件包含enum範圍的最小整型來實現(比如char型別),除非使用編譯器命令‑‑enum_is_int
來強制將enum的基礎型別設為至少和整型一樣寬。超出範圍的列舉值預設僅產生警告:#66: enumeration value is out of "int" range
13. 結構體:struct {
char c;
short s;
int x;
} //這個結構體佔8個位元組
但是,結構體:
struct {
char c;
int x;
short s;
} //這個結構體佔12個位元組
這是為什麼?
對於結構體填充,據定義結構的方式,keil MDK編譯器用以下方式的一種來填充結構:
定義為static或者extern的結構用零填充;
棧或堆上的結構,例如,用
malloc()
或者 auto定義的結構,使用先前儲存在那些儲存器位置的任何內容進行填充。不能使用memcmp()
來比較以這種方式定義的填充結構!
14. 編譯器不對宣告為volatile 型別的資料進行優化。 我發現還有不少剛入門的嵌入式程式設計師從沒見過這個關鍵字.
15. __nop():延時一個指令週期,編譯器絕不會優化它。如果硬體支援NOP指令,則該句被替換為NOP指令,如果硬體不支援NOP指令,編譯器將它替換為一個等效於NOP的指令,具體指令由編譯器自己決定。
16. 還有一些編譯器知識,我放在了另外一篇博文裡,《有趣的keil mdk細節》。
後記:關於Keil MDK的應該掌握的知識,在《編寫優質嵌入式C程式》一文的第三章中,做了進一步總結,可以直接檢視該文。