在 mutli-task 或是 interrupt 中想要使用長整數乘除法是不可以的。因為長整數乘除法會用到額外的全域變數 _mulint_PARM_2,如果同時有二個 task 同時用到長整數乘除法時,就會造成錯亂。
這時候就需要加入 --stack-auto 參數,讓所有編譯都搞成 reentrant 函數,這樣就可以避免使用全域變數的問題。
雖然使用 --stack-auto 可以呼叫 reentrant libray,但是也把使用者的函數全都編譯成 reentrant function 了。這樣雖然沒有什麼大問題,但是在 tiny rtos 因為 stack 空間不大,使用太多 reentrant function 有可能會讓 stack 爆掉。所以只在 SDCC Linker 地方加入 --stack-auto 即可,不要在 SDCC Compiler 加。
奇怪的是卻出現了,在 SDCC Linker 時會有錯誤訊息 ?ASlink-Warning-Undefined Global '__mulint_PARM_2' referenced by module 'ip210s_net';原來在 SDCC Linker 加上 --stack-auto 是不夠的,此時需在 SDCC Compiler 加入 --int-long-reent 才能正確無誤使用長整數乘除法。
前言
由於 IP210S 使用的 flash 有 512KB,所以一定會用到 code banking 的功能,所以這次把 code banking 功能加入 tiny rots 裏。主要新增的部份就是在切換 task 時,會把目前 bank 值 push 到堆疊裏,然後把下一個 task 的 bank 值 pop 出來,並還原 bank 設定。
- IP210S 一開機時會把 A16~A18 設成 0,所以相對應的 port 值在一開始初始化時,不要任意變動。因為只要一變動,address 就會被切換到新的 bank 區。
- 比如我喜歡在初始化時把所有 port 值設成 0xff,所以在初始化過後,bank 會被切換到 bank7。因此在最後一塊必須要有 common code,不然會當機的。
- 由於 demo board 上的 A16~A18 剛好位在二個不同的 port 上,當我從 bank0 切換到 bank7 時,會先設定 A18=1,此時 bank0 會馬上切到 bank4,如果 bank4 剛好沒有 common code,那就馬上當機了,同理設定 A16/A17 值也是如此,所以最好每一個 bank 都要有 common code,不然就是好好注意 A16~A18 設定時的 bank 變化。
- 在 rtos_start 裏記得要多一行指令 *((__idata unsigned char *)rtos_stack[i]) = i;,目的是要把每一個 task 的 bank 值先設定好,免得跑錯 bank 區域了。
- 每一個 task 的起始函數不需要加 __banked,因為那些函數是沒有結束的一刻,所以加不加都無所謂。因為加入 __banked 是要讓編譯器把 call 及 ret 換成 __sdcc_banked_call 及 __sdcc_banked_ret,既然都沒有結束,也不會呼叫到 ret。
前言
最近在玩 IP210S 的晶片,該顆 8051 使用外掛的 flash(SST 39VF040),有 512KB 之大,總共有 19 條 address line。之前寫的 SDCC Bankswitching 太簡略了,所以又提筆補充了一下。
- ip210s 對於 512K flash 的 address line 是放在 P1.7(18),P3.4(17),P3.5(16)。所以只要對該三個 GPIO 做設定,就可以讓 ip210s 定址到 512KB。
- 每個 bank 的起始位址設定指令為 "-Wl-b BANK1=0x18000",BANK1 為 #pragma codeseg 的名稱,0x18000 為該 bank 的起始位址。以 IP210S 為例,512/64=8,所以必須切成 8 banks。"-Wl-b BANK0=0x8000","-Wl-b BANK1=0x18000","-Wl-b BANK2=0x28000","-Wl-b BANK3=0x38000","-Wl-b BANK4=0x48000","-Wl-b BANK5=0x58000","-Wl-b BANK6=0x68000","-Wl-b BANK7=0x78000"。
- #pragma codeseg 的使用只對在它宣告下面的程式碼有用,以上則無效,因為內定值是不同的,所以才需用 #pragma codeseg 來變更。
- 在寫程式時,通常伴隨著一些存放在 code 的變數,如 code char str[] = "hello test\n\r";,一般而言,它們存放的區域也是放在 common 區,如果也把它們放在 bank 裏,則需宣告 #pragma constseg,如此 SDCC 才會把 code 變數也放到 bank 裏。
- SDCC 編譯出來的 hex 檔,包含了 common 及所有的 bank 程式,經由 hex2bin 的轉換後,只有 bank0 包含 common 程式碼,其它 bank 都沒有,只能利用 ultraedit 自己剪貼搞定。
- 使用 SDCC Bankswitching 需要在 main.c 宣告一個變數 unsigned char PSBANK=0;,並要先初始化為 0,這個 PSBANK 主要是存放目前的 bank 值。
__sdcc_banked_call
- 一般函數呼叫都是用 lcall _test1,但是在呼叫 banked 函數時,則是會先呼叫 __sdcc_banked_call,並把 banked 函數位址放到 r0~r2 當參數傳遞。
- 當要呼叫位於 bank? 區段的函數時,會把讓函數的位址放到 r0~r2,由於在 bank? 區段的函數位址包含了最高的 16~18 條位址線,所以會把 r2 當做 bank 的選擇,實際上 r2 只是代表 address line 的 16~23 位址線而已。由此可知,SDCC 的 Bankswitching 可以達到 224,也就 16M bytes。
- 一開始需先把目前的 bank 值(PSBANK)推入堆疊,以便離開時還原 bank 值。
- 由於函數可能利用 acc 來傳遞參數,所以必須先把 acc 值保存,xch a,r0 就是把 acc 保存在 r0。
- 接著把呼叫函數的位址推入堆疊,以便等一下 ret 時跳到該函數位址。
- 接著就是判斷 r2 值來切換位址線,我只會最笨的方法,貽笑大方了。
- 然後還原 acc 值,並用 ret 返回呼叫函數。
__sdcc_banked_ret
- 一般函數結束時都是直接執行 ret 回到上一層,但是在 banked 函數結束時,則是會呼叫 __sdcc_banked_ret,並經由 __sdcc_banked_ret 返回上一層。
- 一開始需先把備份的 bank 值(PSBANK)從堆疊取出,以便等會還原 address line。
- 由於函數可能利用 acc 來傳遞返回值,所以必須先把 acc 值保存,xch a,r0 就是把 acc 保存在 r0。
- 接著就是判斷 PSBANK 值來切換位址線,我只會最笨的方法,貽笑大方了。
- 然後還原 acc 值,並用 ret 返回上一層函數。
前言
在前面一個 tiny rtos 裏只是簡單平均分配時間到每一個 task 裏,但這有一個缺點,就是當某一個 task 需要暫停一下時,無法把時間交給其它的 task,這個時間無形間就被浪費掉了。所以在這一次新的 task 裏,加入了 rtos_delay_10ms 函數,讓 task 可以暫停一段時間,並把時間讓出來給別的 task。
- 把 timer0 的時間都切成 10ms,每一個 task 的運行時間都變成 10ms。
- 多了一個 rtos_tick 變數做 delay 的計數,以 10ms 為單位。
rtos_start
- 原本儲放 stack 的變數多了一個,用來放置 rtos_idle 的 stack。
- 第一個執行的 task 改成 rtos_idle,而非 task1。
- 由於 rtos_idle 沒做什麼事,所以只留一份必要的 stack 空間給它,其它的都分給其它 task。
- 不可以在此函數裏呼叫任何函數,因為呼叫函數會變更 stack 裏的內容(有 push 動作),這樣會把 task1 的 stack 破壞,造成進入 task1 時位置被變更,而跑不到 task1。
- 如果按照之前的方式,直接跳到 task1 執行,而不是從 rtos_idle 執行,就可以在此函數內呼叫其它函數,因為 stack 被破壞並不會影嚮 task1 的執行。
rtos_idle
- rtos_idle 裏其實不沒做什麼事,就是一直進入 idle 模式以利省電。
- rtos_idle 裏不可以呼叫任何函數,因為保留給它的 stack 空間只夠做 task 切換而已,容不下其它的 push 指令。
rtos_nexttask
- rtos_nexttask 新增了計數功能,用來計算每一個 task delay 時間,當 rtos_tick==0 時,該 task 才擁有執行時間。
- 如果所有 task 都處於閒置狀態,就進入 idle task 等待其它 task 被喚醒。
- rtos_nexttask 裏不可以呼叫任何函數,因為如果遇到從 rots_idle 進入的 stack,它的 stack 空間只夠做 task 切換而已,容不下其它的 push 指令。
執行結果
extern unsigned char *rtos_stack 跟 extern unsigned char rtos_stack[] 的差異
Posted
by: 邱小新
at 上午11:37
in
SDCC
extern __xdata unsigned char *rtos_stack;
extern __xdata unsigned char rtos_stack[];
原來的變數為 __xdata unsigned char rtos_stack[3],很明顯的在 SDCC 裏 (char *) 不能等同於 (char []),這點要特別注意。
前言
使用 SDCC 來寫 rtos 比起用 keil-c 來得方便許多,因為 SDCC 有一個 naked 修飾元。naked function 會省略前置的 push 動作及最後的 pop 及 ret 指令,這些動作都需要使用者自己加入。在 sdccman.pdf 內的說明指出 naked function 主要是用於 interrupt function 來省略一堆無意義的 push/pop 動作,剛好被我用來增加 push/pop 動作(完全相反的立意)。
rtos_start
- 其實我寫的 rtos 很簡單,也就是把 stack 分成四份,配給四個 task,讓每個 task 的 stack 各自獨立不互相干擾。
- 再利用 timer 0 的中斷處理來切換 task。
- rtos_task_id 是記錄目前正在執行的 task 是那一個。
- rtos_stack 是記錄每一個 task 的 stack 位置。
- 由於在切換 task 時,會 pop 14 個 register 及 task 的執行位置。所以一開始需要把每個 task 的起始位置 push 到堆疊里,並把 stack 起始位置加上 14,在做切換 task 時,才會順利執行相應的函數。
- 由於第一個 task 就是主程序,所以不用做 push 動作,因為在第一次切換 task 時,會自動 push,因此才會有 rtos_stack[0] = SP; 的指令。
- call task1() 及 call rtos_start() 理論上會浪費 stack 空間才對,但因為被 SDCC 最佳化的關係,二個指令都變成 ljmp task1 及 ljmp rtos_start,並沒有浪費到 stack 空間,請安心使用。
rtos_nexttask
- rtos_nexttask 的動作很簡單,就是保存現在所有的 register (r0~r7, acc, b, dph, dpl, psw, bp)及目前的 sp 位置,然後變更到下一個 task 的 sp,再還原所有的 register,最後 return 到新 task 的位置(在 rtos_start 時已經記錄到 堆疊里)。
- bp 這個東東應該不算 register,這個主要是被用在 reentrant 函數,一個很特殊的全域變數,忘了保存它還會造成程式錯亂喔。
執行結果
注意事項
- 由於是多工的系統,所以對於 SDCC 內建的 library 的使用要求就是要有 --stack-auto 的選項。而內建的 library 只有 small-stack-auto,所以要在 sdcc-eclipse 的 SDCC Linker Command 選項加入 sdcc --model-small --stack-auto 才會選用 small-stack-auto library。
- 至於 SDCC Compiler Command 要不要加 --stack-auto 都可以,如果沒有加入此參數,則需自己判斷是否為可重入函數,在函數宣告後面加入 __reentrant 關鍵字。
- 為了避免 task 之間的 local variable 因為 overlay 的關係互相干擾,所以要在 sdcc-eclipse 的 SDCC Compiler Command 選項加入 --nooverlay 參數。
- PUSH 指令是先將 SP 加一,再將直接定址資料放入堆疊空間裏。相反的,POP/RET 指令是先把堆疊空間資料取出放到直接定址空間,再將 SP 減一。
- 在 tiny rots 內不要使用 printf,儘可能使用 printf_tiny 代替。因為 printf 會用到 bit variable,造成 stack 空間減少。
- 確定會被不同 bank 呼叫的函數才需要加 __banked,否則不用浪費堆疊空間去記錄相同的 PSBANK 值。
- 一般的初始函數可以放在 bank0,而且也不用加 __banked,這樣可以把 common area 縮小。
- common area 越小,相對的每個 bank 可用的空間越大。
- __reentrant 函數的建立要視函數內的變數是否被轉換成 idata 變數,如果區域變數都是使用 register,那麼就不需要加 __reentrant,加了還可以能浪費到 stack 空間。
- 要減少函數用到 idata 變數,可以試著關掉編譯器的最佳化功能,比如 #pragma nogcse 等。
- ...
由於 8051 單晶片內部的堆疊空間有限,所以無法像 windows/unix 那樣使用堆疊。一般在 C 語言裏,呼叫函數時會將函數參數及函數使用的局部變數放入堆疊。因為 8051 堆疊空間有限,所以無法使用此方式,而是為每個函數的局部變數及參數設定一個空間來存放。比如下面的範例就是如此,參數 a 及 b 被分配到二個全域變數 _test_a_1_1 及 _test_PARM_2。
呼叫方式也是直接把傳遞值直接存入全域變數 _test_PARM_2 裏。
SDCC 把局部變數被放到全域變數裏,相當於被宣告成靜態變數一樣,只是差在沒宣告 static 而已。由於此種原因,造成此類函數變成不可當成遞迴函數來使用,也就是不能自我呼叫函數。而且在 interrupt function 裏不可以呼叫此類函數,因為當中斷發生在此類函數時,interrupt function 又呼叫一次原來函數,會造成局部變數被修改,而造成不可預期的錯誤。所以需要在函數宣告後加上 __reentrant,讓編譯器按照一般 C 語言方式把局部變數都放到堆疊裏。下面的函數就是如此,把局部變數放到堆疊裏(sp-4)。
呼叫時,直接把傳遞值放入堆疊裏,而且在呼叫結束也會還原堆疊指針。
各位有沒有在 reentrant function 裏發現一個不名的變數 _bp,用來做堆疊指標的運算,以取得參數的值。而這個 _bp 變數是宣告在 SDCC\lib\src\_bp.c --> __data unsigned char bp;。而且這個變數還被 printf_tiny 及 printf_fast 所使用,當然這二個函數有宣告成 __reentrant 函數,使用 _bp 變數也只是變相利用而已啦。
請注意下列二行程式碼有啥不同?
沒錯,就是括弧位置不同,但是請看下列編譯出來的組合碼。
注意到那不同處的地方了吧,二者算出來的值卻相差十萬八千里,其實在指標使用時,這是一個很容易犯錯的地方。當你原本只是要取一個結構結尾的指標,很自然的會寫出 1 的程式碼,但那是錯誤的。因為指標加法是根據指標的變數型態去相加,比如一個 short 指標加一,是指向下一個 short 位置,也就是指數加二。而在此案例上,第一種寫法是指向第8個結構的位置,也就 8x8=64=0x40 的位置,實在是大錯特錯。正確寫法為第2,3個寫法。
二的補數法(2's complement)是電腦中為了儲存及運算有正負號的整數而使用的一種方法,要用二進位表達一個負整數的方法很多,但是二的補數表示法使得負整數與正整數的運算(加、減、及乘法)完全一致,不需要額外的硬體來處理。
範例
以 4 位元二的補數數字來說。例如:1 + (-2) = -1,0001(1) + 1110(-2) = 1111(-1)。你可以發現二的補數的加法只要使用標準的二進位加法就可以了。
| 2 進位 | 無號整數 | 有號整數 |
| 1111 | 15 | -1 |
| 1110 | 14 | -2 |
| 1101 | 13 | -3 |
| 1100 | 12 | -4 |
| 1011 | 11 | -5 |
| 1010 | 10 | -6 |
| 1001 | 9 | -7 |
| 1000 | 8 | -8 |
| 0111 | 7 | 7 |
| 0110 | 6 | 6 |
| 0101 | 5 | 5 |
| 0100 | 4 | 4 |
| 0011 | 3 | 3 |
| 0010 | 2 | 2 |
| 0001 | 1 | 1 |
| 0000 | 0 | 0 |
十進制轉成二進制
以 -3 為例。
- 11: 先把數字部分轉換成二進制。
- 0011: 依照 bit 數,補齊空缺。
- 1100: 使用1的補數,將1變0,0變1。
- 1101: 再把1的補數加上1,形成2的補數。
二進制轉成十進制
以 1101 為例。
- 1100: 2的補數減1,還原成1的補數。
- 0011: 1變0,0變1。
- 3: 二進制轉成十進制。
- -3: 數字前加上負號。
while (i--)
while (--i)
結論
這樣結果很明顯吧,i-- 比 --i 多用了一個 r6 register,而且也多了二個指令,下次記得寫 while (--i)。
最近在寫一段程式碼,剛好有去注意它編譯出來的assembly code,發現怎麼沒有預期出來的結果。結果發現卻是運算子優先順序的問題。切記位元運算的優先權為 shift > and > xor > or,之前一直以為三者是一樣大,原來卻不是如此。希望以前寫的產品不要因為如此而出問題啊,阿彌陀佛,善哉善哉。
原始錯誤程式碼運算子優先順序
| 優先 順序 | 評估順序 | 運算子 | 說明 |
15 | 由左至右 | ., [], () | 欄位存取、陣列索引、函式呼叫和運算式群組 |
14 | 由右至左 | ++, --, -, ~, !, delete, new, typeof, void | 一元運算子、傳回資料型別、物件建立、未定義的值 |
13 | 由左至右 | *, /, % | 乘法、除法、modulo 除法 |
12 | 由左至右 | +, - | 加法和字串串連、減法 |
11 | 由左至右 | <<, >>, >>> | 位元移位 |
10 | 由左至右 | <, <=, >, >=, instanceof | 小於、小於或等於、大於、大於或等於、instanceof |
9 | 由左至右 | ==, !=, ===, !== | 等號比較、不等比較、嚴格等號比較和嚴格不等比較 |
8 | 由左至右 | & | 位元 AND |
7 | 由左至右 | ^ | 位元 XOR |
6 | 由左至右 | | | 位元 OR |
5 | 由左至右 | && | 邏輯 AND |
4 | 由左至右 | || | 邏輯 OR |
3 | 由右至左 | ?: | 條件式 |
2 | 由右至左 | =, OP= | 指派、複合指派 |
1 | 由左至右 | , (逗號) | 多重評估 |
在 eclipseSDCC 內要編譯 c source 是完全沒問題的,但是要編譯 assembly source 卻產生一個小小的 bug,很多人都有在問解決方法,但原作者似乎沒有要修改的意願,還好有一個熱心的使用者提供一個解決方法,可以完美的解決。
在最原始的設定下,直接編譯 asm 會出現下列錯誤。
為什麼找不到 as51?那是因為 SDCC 的 assembly compiler 應該 asx8051 才對,換成 asx8051 後,再次編譯 asm 則出現下列錯誤。
為啥會錯呢?那是因為 -o 還沒有支援路徑功能,在那編譯,就在那產生 rel,不能更改路徑。最後使用 domning 所提供的 SDCCAsmWrapper 才得以正確的編譯成功。
安裝 SDCCAsmWrapper
- 下載 SDCCAsmWrapper。
- 解壓縮到 C:\Program Files\SDCC\bin。
- 修改 SDCC Assembler Command 為 SDCCAsmWrapper 即可。
- SDCCAsmWrapper 會去呼叫 asx8051 做編譯,再 copy rel 到 output 目錄。
SDCC 函數回傳值
- SDCC 使用 DPL, DPH, B, ACC 來做回傳值的傳遞。
- 回傳 1 byte : 使用 DPL。
- 回傳 2 bytes: 使用 DPL(LSB) 及 DPH(MSB)。
- 回傳 3 bytes: 使用 DPL, DPH, B。
- 回傳 4 bytes: 使用 DPL, DPH, B, ACC。
- generic pointers 回傳 3 bytes,指針位置存在 DPL(LSB) 及 DPH(MSB),指針型態存在 B,0x00–xdata/far, 0x40–idata/near, 0x60–pdata, 0x80–code。
SDCC 函數參數值
- 第一參數: 使用 DPL, DPH, B, ACC 來傳遞,如同回傳值一般。
- 其餘參數: reentrant 函數使用 stack,一般函數使用 data/xdata,視記憶體模式而定。
- bit 參數都是使用 bit-addressable 位置的空間,不論 reentrant 或一般函數。
要做 SDCC code banking 真是有夠麻煩,找不到有人分享成功案例就算了,sdccman.pdf 竟然也只提到一頁而已,完全沒法子去了解如此實做。最後靠著不斷的試驗,總算完成這一件不可能的任務。
- 修改 C:\Program Files\SDCC\lib\src\mcs51\crtbank.asm 來達成 bankswitching 的函數。
- 重新建立 mcs51.lib,把修改好的 crtbank.asm 置換掉舊的 mcs51.lib。
- Project Properties → C/C++ Build → Settings → Tool Settings → SDCC Linker → Command 輸入 sdcc -Wl-r "-Wl-b BANK1=0x18000",千萬不要輸入 sdcc "-Wl-r -Wl-b BANK1=0x18000",為此我花了半天找問題,真是 ooxx。
- 建立一個 c source file,開頭輸入 #pragma codeseg BANK1。
- 建立一個函數 void test1(void) banked { ... },要記得加上 banked。
- 轉換 ihx 成 bin,makebin.exe < test.ihx > test.bin
- 產生出來的 bin,裏面有 common/bank0/bank1,但是在 bank1 裏沒有 common 存在,需要手動加進去,而且也要手動分開這個 bin,才方便 winbond isp writer 燒錄。
- 大功告成,真是幸苦啊,花了一天才完成。
to be continue.... 後面有空再來補一些說明及範例吧。
今天為了試驗 SDCC code banking,一直在試 crtbank.asm,怎麼弄都不行,最後想到直接置掉 mcs51.lib 看看。結果,當我下 sdcclib -m mcs51.lib,竟然回我一個錯誤訊息。
最後,直接下 sdcclib -a mcs51.lib crtbank.rel,把檔案暴力加進去,結果....裏面檔案都不見了,只剩下 crtbank.rel
當然這樣是不能用的啦,其它的 startup code 都不見了,根本無法編譯。所以又手動把 C:\Program Files\SDCC\lib\src\mcs51 下的 asm 全部編進 mcs51.lib。傻眼的是雖然可以加到 SDCC,但是搞出來的 hex 檔燒進去竟然動不了,真是怪了。
只好又去拜酷狗大神,歷經千幸萬苦,最後找到原始編譯 mcs51.lib 才解決了問題。原來我一直使用 asx8051 -ol 是不對的,應該下 asx8051 -plosgff 才對。而且 sdcclib 也不用加 -a 參數。
SDCC
| 記憶體區塊 | 指向位址 | 變數型別 | 變數名稱 | 初始值 |
| __xdata | [__at 0xC000] | unsigned char | test_data | [=0x01] |
指向位址及初始值可以不宣告。指向位址沒有宣告的話,則由編譯器自動分配位置給變數,但由於 sfr 的特殊性,所以一定要包含 __at。
- data / near
// c code __data unsigned char test_data=0x01; // asm code mov _test_data,#0x01 - xdata / far
// c code __xdata unsigned char test_xdata=0x01; // asm code mov dptr,#_test_xdata mov a,#0x01 movx @dptr,a
未完待續......
Keil C51
新的定義方式。
| 變數型別 | 記憶體區塊 | 變數名稱 | 初始值 |
| unsigned char | xdata | test_data | [=0x01] |
舊的定義方式,不建議使用。
| 記憶體區塊 | 變數型別 | 變數名稱 | 初始值 |
| xdata | unsigned char | test_data | [=0x01] |
指向絕對位址定義方式,不可直接給初始值。
| 變數型別 | 記憶體區塊 | 變數名稱 | 指向位址 |
| unsigned char | xdata | test_data | _at_ 0xC000 |
但是如果一定要在指向絕對位址時又同時給初始值呢?? 請看http://blog.sina.com.cn/s/blog_4c5cec3b0100dmtd.html。
未完待續......
函數指標陣列
- 陣列宣告
typedef int (*pt2Function)(int, int) __reentrant; pt2Function funcArr1[10]; int (*funcArr2[10])(int, int) __reentrant; funcArr1 跟 funcArr2 宣告結果是一樣的,利用 typedef 比較容易理解。
- 範例程式
int Plus(int a, int b) __reentrant { return a+b; } int Minus(int a, int b) __reentrant { return a-b; } void main() { int (*funcArr[10])(int, int) __reentrant; printf("\nExecuting 'Array_Of_Function_Pointers'\n"); funcArr[0] = &Plus; funcArr[1] = &Minus; printf("12 + 5 = %d\n", funcArr[0](12, 5)); printf("12 - 5 = %d\n", (*funcArr[1])(12, 5)); }
函數指標當成回傳值
- 函數宣告
typedef int(*pt2Func)(int, int) __reentrant; pt2Func GetPtr1(const char opCode); int (*GetPtr2(int, int) __reentrant)(const char opCode); GetPtr1 跟 GetPtr2 宣告結果是一樣的,利用 typedef 比較容易理解。其中的 __reentrant 也可以不用宣告,因為只是函數指標傳遞並不影響函數中的區域變數的使用。
- 程式範例
int Plus(int a, int b) __reentrant { return a+b; } int Minus(int a, int b) __reentrant { return a-b; } int (*GetPtr1(int, int))(const char opCode) { if(opCode == '+') return &Plus; else return &Minus; } void main() { int (*pt2Function)(int, int) __reentrant = NULL; printf("Executing 'Return_A_Function_Pointer'\n"); pt2Function=GetPtr1('+'); printf("2+4=%d\n", (*pt2Function)(2, 4)); pt2Function=GetPtr1('-'); printf("2-4=%d\n", (*pt2Function)(2, 4)); }
函數指標當成參數傳遞
- 參數宣告
void PassPtr(int (*pt2Func)(int)); void PassPtr(int (*pt2Func)(int, char, char) __reentrant); - 程式範例
int DoIt(float a, char b, char c) __reentrant { printf("DoIt\n"); return a+b+c; } int DoMore(float a, char b, char c) __reentrant { printf("DoMore\n"); return a-b+c; } void PassPtr(int (*pt2Func)(float, char, char) __reentrant) { int result = (*pt2Func)(12, 'a', 'b'); printf("%d\n", result); } void main() { printf("Executing 'Pass_A_Function_Pointer'\n"); PassPtr(&DoIt); PassPtr(&DoMore); }
