# 韌體工程師的0x10個問題
### preprocessor(預處理器)
* 用於在編譯器處理程式之前預掃描原始碼,完成標頭檔包含, 巨集擴展, 條件編譯, 行控制(line control)等操作。
* 以 # 開頭都是前置處理器,例如:#include, #define
### 巨集(macro)
* 我們可以運用巨集,來消除重複,也就是透過前置處理器指令去**定義文字替換的規則**,產生另一組程式碼,甚至還能創造語言本身沒有的語法
* 但是,巨集撰寫、除錯或者是維護,不是一件容易的事情。因為要讓巨集能適用諸多場合,往往必須加入更多額外的巨集程式碼,或者是特定環境的條件編譯,令巨集變得複雜。
### #define 識別名稱 代換標記
* **識別名稱**:即替換內容的縮寫,一般來說是使用大寫較易辨認。
* **代換標記**:通常可以是常數、字串或是函數。
### 1. 用預處理指令#define 聲明一個常數,用以表示1年中有多少秒 (忽略閏年問題)
```c=
#define SECONDS_PER_YEAR(60 * 60 * 24 * 365)UL
or
#define SECONDS_PER_YEAR(60UL * 60UL * 24UL * 365UL)
```
含意:
1. `#define`語法的基本知識,如**不能以分號結束、括號的使用**。
2. 了解到預處理器會去計算常數的運算式,因此它是簡潔的,比起你自己真的去計算一年中有多少秒數
3. 了解到運算式可能會在一個16bit的機器上對整數產生溢位(>2^16),因此需要用L,告訴編譯器對待運算式為Long。
4. 如果你更改運算式為UL(意指unsigned long),你會有一個好的開始,因為你注意到了signd與unsigned型態的危險。
#### * 補充
| 型態 | 大小(bytes) |
| ----------- |:-----------:|
| short | 2 |
| int | 4 |
| long | 4 |
| float | 4 |
| double | 8 |
| long double | 16 |
| char | 1 |
### 2. 寫一個"標準的" MIN macro。這個macro接收兩個參數並且回傳這兩個參數中較小的那個
```
#define MIN(A,B) ((A) <= (B) ? (A):(B))
```
含意:
1. 對於`#define` 用在macros中的基礎知識。這很重要,因為直到inline operator變成標準C的一部分之前,巨集是方便產生行內程式碼的唯一方法。對於嵌入式系統來說,為了能達到要求的性能,行內程式碼經常是必須的方法。
2. 三元運算子的知識。(`a ? b : c` 當a為真則執行b,當a為否則執行c。)
這個運算子存在在C語言中的原因是,它使得編譯器能產生比if-then-else更優化的程式碼,了解這個用法是很重要的。
3. 懂得在巨集中小心地把參數用括號括起來。以及討論巨集的副作用,例如下面這個程式碼會發生什麼問題?
```
least=MIN(*p++,b);
```
Ans : `*p++`在進行比較時和輸出結果時的值會不一樣,`p++`會執行兩次。(我的想法)
### 3. 預處理指令#error的目的是什麼?
#error就是生成編譯錯誤的訊息,然後會停止編譯,可以用在檢查程式是否是照自己所預想的執行。其語法格式為:
```
#error error-message
```
### 無窮迴圈(Infinite loops)
### 4. 無窮迴圈常出現在嵌入式系統。如何用C寫一行無窮迴圈的程式
1. 首選方案是:
```
while(1){
...
}
//
while(邏輯表達式){
反覆執行的語句
}
```
只要邏輯表達式為真,就會反覆執行大括弧裡面的語句,直到邏輯表達式為假,循環結束,只要把邏輯表達式寫成1則循環成為死循環。
原文網址:https://kknews.cc/code/r9aqx4o.html。
2. 另一種常見的寫法:
```
for(;;){
..
}
```
這是K&R(C語言書的作者)喜歡用的方式,並且是唯一一個方法來達成無窮迴圈且通過Lint。
3. 第三個方法是使用goto:(組合語言)
```
Loop:
...
goto Loop;
```
### 5. 使用變數a,給出下面的定義:
#### a)一個整型數 (An integer)
#### b)一個指向整數的指標 (A pointer to an integer)
#### c)一個指向指標的指標,它指向的指標是指向一個整型數(A pointer to a pointer to an integer)
#### d)一個有10個整數型的陣列 (An array of 10 integers)
#### e)一個有10個指標的陣列,該指標是指向一個整數型的(An array of 10 pointers to integers)
#### f)一個指向有10個整數型陣列的指標 (A pointer to an array of 10 integers)
#### g)一個指向函數的指標,該函數有一個整數型參數並返回一個整數 (A pointer to a function that takes an integer as an argument and returns an integer)
#### h)一個有10個指標的陣列,該指標指向一個函數,該函數有一個整數型參數並返回一個整數 (An array of ten pointers to functions that take an integer argument and return an integer)
Ans:
a) int a;
b) int *a;
c) int **a;
d) int a[10];
e) int *a[10];
f) int (*a )[10];
g) int (*a)(int);
h) int (*a[10])(int);
### 6. 關鍵字static的作用是什麼?
static有三種不同的用法:(好處)
1. 在函數區間內(in funtion block),一個被宣告為靜態的**變數**,在函數被呼叫的過程中其值維持不變。
2. 在一個Block(ie. {...} )內 (但在函數外),一個被宣告為靜態的**變數**可以被Block內所有的函數存取,但不能被其他Block中的函數存取。它是一個本地的全局變量。(local的global變量)
3. 在一個block內,一個被宣告為靜態的**函數**,只可以被這一個block內的其他函數呼叫。也就是這個函數被限制在宣告它的Block的(本地)範圍內使用。
------
//另一種答案//
1. function內做靜態變數宣告,在這一函數被呼叫的過程中其值維持不變
2. function外做靜態變數宣告或靜態函數宣告,代表此變數或此函數只能被同file的函數做存取
#### * [補充](https://www.itread01.com/article/1447398632.html)
在C語言中,static的三種作用:
1. 隱藏功能,利用這一特性可以在不同的檔案中定義同名函式和同名變數,而不必擔心命名衝突。
2. 保持變數內容的持久,儲存在靜態資料區的變數會在程式剛開始執行時就完成初始化,也是唯一的一次初始化。
3. 預設初始化為0,其實全域性變數也具備這一屬性,因為全域性變數也儲存在靜態資料區。在靜態資料區,記憶體中所有的位元組預設值都是0x00。
### 7. 關鍵字const有什麼含意?
const 代表 “read-only”。資料型別被const修飾的變數在「初始化」之外,不能再被賦予值。
(合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的程式碼修改。)
#### 附加問題,這些未完成的宣告代表什麼?
1. const int a;
2. int const a;
3. const int *a;
4. int * const a;
5. int const * a const;
含意:
1. a是一個常數型整數
2. 同(1)
3. 一個指向常數型整數的指標(整數無法修改,但指標可以)
4. 一個指向整數的常數型指標(指標指向的整數可以修改,但指標是不可以修改的)
5. 一個指向常數型整數的常數型指標(指標指向的整數不可以修改,同時指標本身也是不可以修改的)
### 8. 關鍵字volatile(易變的)有什麼含意?並給出三個不同的例子。
一個被定義為volatile的變數代表說這個變數會不可預期的被改變。因此,編譯器就不會去假設這個變數的值了。具體來說,編譯器在用到這個變數時,每次都會小心地重載這個變數,而不是使用保存在暫存器的備份。Volatile變數的範例如下:
( a ) 周邊設備的硬體暫存器,如狀態暫存器
( b ) 中斷服務函式(ISR)中會訪問到的非自動變數(Non-automatic variables)
( c ) 多執行緒應用中,被多個任務共享的變數
> 例子:
1. 並行設備的硬體暫存器 (如︰狀態暫存器)
2. ISR會修改的全域變數或static變數
3. 多執行緒下被多個task共享的變數
/volatile變數代表其所儲存的內容會不定時地被改變,宣告volatile變數用來告訴編譯器 (Compiler) 不要對該變數做任何最佳化操作,凡牽涉讀取該volatile變數的操作,保證會到該變數的實體位址讀取,而不會讀取CPU暫存器的內容 (提升效能) 。舉個例子,某一硬體狀態暫存器 (Status Register)就必須宣告volatile關鍵字,因為該狀態暫存器會隨時改變,宣告volatile便可確保每次讀取的內容都是最新的。/
#### 進階問題:
( a )一個參數可以同時是const也是volatile嗎?解釋為什麼。
( b )一個指標可以是volatile 嗎?解釋為什麼。
( c )下面的函數有什麼錯誤︰
```
int square(volatile int *ptr){
return *ptr * *ptr;
}
```
答案如下:
( a )是的,舉例說明像是"read only的狀態暫存器"。它是volatile,因為它可能會被非預期的改變;它是const,因為程式不應該試圖修改它。
( b )是的,儘管這並不常見。一個例子是中斷服務函式修改一個指向buffer的指標時。
( c )這段程式碼的目的是用來返指標***ptr指向值的平方**,但是,由於 *ptr指向一個volatile型參數,編譯器將產生類似下面的程式碼︰
```c=
int square(volatile int *ptr){
int a, b;
a = *ptr;
b = *ptr;
return a*b;
}
```
因為*ptr的值可能會被不預期的改變,因此a和b可能是不同的。所以,這段程式碼可能返回不是你所期望的平方值!正確寫法的程式碼如下︰
```c=
int square(volatile int *ptr){
int a;
a = *ptr;
return a*a;
}
```
### 位元操作(Bit operation)
在 C 中提供的位元運算子,分別是AND、OR、NOT、XOR與補數。
### 9. 嵌入式系統總是需要使用者去操作在暫存器或者變數中的位元。
給一個整數變數a,寫出兩段程式碼。第一個要設定a的bit3,第二個要清除a的bit3。在以上兩個操作中,要保持其它位不變。
Ans:
用 #defines 和 bit masks 操作。解決方法如下:
```c=
//寫法一
#define BIT3(0x1<<3)
static int a;
void set_bit3(void){
a |= BIT3;
}
void clear_bit3(void){
a &= ~BIT3;
}
--------------
//寫法二
#define BIT3 (1U << 3)
int main()
{
int a = 0;
/* set bit 3 */
a |= BIT3;
/* clear bit 3 */
a &= ~BIT3;
}
```
另一種用macro的寫法:
```c=
#define SET_BIT(p,n) ((p) |= (1 << (n)))
#define CLEAR_BIT(p,n) ((p) &= ~(1 << (n)))
#define FLIP_BIT(p,n) ((p) ^= (1 << (n)))
#define CHECK_BIT(p,n) ((p) & (1 << (n))
```
重點是要看到明白的常數,以及使用 |= 和 &= ~結構。
### 10. 嵌入式系統經常具有要求程式員去存取某特定的記憶體位置的特點。
在某個專案中被要求設定一個絕對位址在0x67a9的整數變數為數值0xaa55。編譯器是一個純ANSI編譯器。寫下程式碼來完成這個任務。
這個問題測試你是否知道為了存取一個絕對位置,去**型別轉換一個整數**成一個指標這是合法的。確切的語法根據每個人的風格因人而異。典型的程式碼如下:
```c=
int main(){
int *ptr = (int *)0x67a9;
*ptr = 0xaa55;
}
```
### 中斷(interrupt)
當CPU在執行程式時,遇到外部或內部的緊急事件須優先處理,因此暫停執行當前的程式,轉而服務突發的事件。直到服務完畢,再回到原先的暫停處(記憶體地址)繼續執行原本尚未完成的程式。
### 11. 中斷是嵌入式系統很重要的一部分。因此,很多編譯器供應商提供一個標準C的擴展來支持中斷。
典型的,這個新的key word是__interrupt。以下的程式碼使用__interrupt來定義一個中斷service routine。評論這個程式碼:
```c=
__interrupt double compute_area(double radius)
{
double area = PI*radius*radius;
printf("\nArea=%f", area);
return area;
}
```
這個函數有太多錯誤了,問題如下:
1) ISR不能返回一個值
2) ISR不能傳遞參數
3) 在許多編譯器/處理器中,浮點數操作是不可重入的(re-entrant)。有些處理器/編譯器需要讓多餘的暫存器入棧(PUSH入堆疊),有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。
4) 與第三點類似,printf通常會有可重入和效能的問題。
### 12. 下方的程式碼將會輸出什麼,並且為什麼會這樣輸出?
```c=
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a + b > 6) ? puts(">6"):puts("<=6");
}
```
這個問題測試你是否懂得C語言中的整數自動轉型原則。
這題的答案會輸出“> 6”。因為當表達式中存在singed與unsinged型態的時候,所有的運算元都會自動轉換為**無符號類型**(unsigned)。
因此–20變成了一個非常大的正整數,並且這個表達式計算出的結果大於6。這是個在嵌入式系統非常重要的點,因為unsigned的資料型態應該會被頻繁的使用。
### 13. 評論以下的程式碼片段?
```c=
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/* 1's complement of zero */
```
對於一個int型不是16位元的機器來說,它將會導致錯誤。
正確的程式碼如下:
```c=
unsigned int compzero = ~0;
```
這個問題真的可以知道應試者是否了解字長在處理器的重要性。好的嵌入式程式設計師會清楚的知道硬體的細節與它的限制,然後電腦程式設計師傾向忽視硬體,並把它視為一個無法避免的煩惱。
### 14. 儘管不像非嵌入式計算機那麼常見,嵌入式系統還是有從堆積(heap)中動態分發內存的過程的。那麼嵌入式系統中,動態分發記憶體可能發生的問題是什麼?
會發生的問題像是記憶體碎片,碎片收集(垃圾回收)的問題,變量的生命週期(變數的執行時間)等等。
### 15. Typedef 頻繁的在C語言中使用,來對一個已經存在的資料型態宣告為同義字。也可以用預處理器做類似的事。
例如,思考一下下面的例子︰
```
#define dPS struct s *
typedef struct s * tPS;
```
以上兩種情況都是要定義dPS和tPS為一個指向結構s的指標。哪個方法比較好,並解釋為什麼?
Ans:typedef更好。思考下面的例子:
```
dPS p1, p2;
tPS p3, p4;
```
第一個式子會被擴展成`struct s * p1, p2;`
上面程式碼定義p1為一個指向結構的指標,p2為一個實際的結構變數,這並不是我們原本想要的。
第二個式子正確定義了p3和p4兩個指標。
### 16. C語言允許一些令人驚訝的結構。這個結構合法嗎?如果合法的話這段程式碼會做什麼?
```c=
int a = 5, b = 7, c;
c = a+++b;
```
根據“maximum munch”原則,編譯器應當能儘可能處理所有合法的用法。因此,上面的程式碼被處理成︰
```
c = a++ + b;
```
因此,在這個程式碼執行之後,a = 6, b = 7 & c = 12;
(其實a++ 就是後做,先運算完之後再++)
---
## 其他筆記
* malloc 函數
> malloc 代表 memory allocation,用來配置指定大小的記憶體空間,傳回新空間第一個位元組的記憶體位址,配置的空間處於尚未初始化的狀態
malloc int 一維陣列
int *ptr = (int *) malloc(sizeof(int) * 3);
* free 函數
> 釋放之前使用 malloc 或 calloc 函數所配置的記憶體空間。
---
### [](https://stackoverflow.com/questions/8208021/how-to-increment-a-pointer-address-and-pointers-value/8208106#8208106)How to increment a pointer address and pointer's value?
```
p++; // use it then move to next int position
++p; // move to next int and then use it
++*p; // increments the value by 1 then use it
++(*p); // increments the value by 1 then use it
++*(p); // increments the value by 1 then use it
*p++; // use the value of p then moves to next position
(*p)++; // use the value of p then increment the value
*(p)++; // use the value of p then moves to next position
*++p; // moves to the next int location then use that value
*(++p); // moves to next location then use that value
```
---
## 程式題目
1+2+4+7+...+106 的總和?
```
#include <stdio.h>
int main()
{
int Sum = 0,i = 1,j = 1;
while(i <= 106){
Sum = Sum + i;
printf("i=%d Sum=%d\n", i, Sum);
i = i + j;
j = j + 1;
}
printf("Sum=%d", Sum);
system("pause"); //可不寫,類似於視窗中的請按任意鍵繼續(?)
return 0;
}
```
## 資料參考
https://medium.com/@racktar7743
https://creteken.pixnet.net/blog/post/24524138-c%E8%AA%9E%E8%A8%80%E6%B8%AC%E8%A9%A6-%E6%87%89%E7%9F%A5%E9%81%93%E7%9A%840x10%E5%80%8B%E5%9F%BA%E6%9C%AC%E5%95%8F%E9%A1%8C