函數指標是實現回呼函數的基礎,回呼函數是C程式架構的基礎。所以函數指標的重要性不言而喻。然而最近在ARM開發板上測試程式時,發現函數指標雖然能編譯通過,但是運行時確總是報錯。如下是一個最簡單的測試程式源碼。
static void test(){ puts("test\n");}static void(*f)() = test;void Main(void){ led_init(); key_init(); io_init(0); f(); while(1){ puts("-------hello from main()----------\n"); delay(100000); }}
使用gcc -S編譯得到的彙編代碼如下:
.cpu arm10tdmi .eabi_attribute 20, 1 .eabi_attribute 21, 1 .eabi_attribute 23, 3 .eabi_attribute 24, 1 .eabi_attribute 25, 1 .eabi_attribute 26, 2 .eabi_attribute 30, 6 .eabi_attribute 34, 0 .eabi_attribute 18, 4 .file "main.c" .section .rodata .align 2.LC0: .ascii "test\012\000" .text .align 2 .syntax unified .arm .fpu softvfp .type test, %functiontest: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 1, uses_anonymous_args = 0 push {fp, lr} add fp, sp, #4 ldr r0, .L2 bl puts nop pop {fp, pc}.L3: .align 2.L2: .word .LC0 .size test, .-test .data .align 2 .type f, %object .size f, 4f: .word test .section .rodata .align 2.LC1: .ascii "-------hello from main()----------\012\000" .text .align 2.LC1: .ascii "-------hello from main()----------\012\000" .text .align 2 .global Main .syntax unified .arm .fpu softvfp .type Main, %functionMain: @ args = 0, pretend = 0, frame = 0 @ frame_needed = 1, uses_anonymous_args = 0 push {fp, lr} add fp, sp, #4 bl led_init bl key_init mov r0, #0 bl io_init ldr r3, .L6 ldr r3, [r3] blx r3.L5: ldr r0, .L6+4 bl puts ldr r0, .L6+8 bl delay b .L5.L7: .align 2.L6: .word f .word .LC1 .word 100000 .size Main, .-Main .ident "GCC: (GNU) 6.2.0" .section .note.GNU-stack,"",%progbits
可見函數指標調用時,被編譯成了BLX指令。這個指令是跳轉並且換運行狀態(ARM和THUMB切換)。顯然這樣編譯是錯誤的,還請高手幫忙分析一下,到底是哪裡出了問題。
是需要特殊的編譯參數嗎。
頭疼。。。
2016-12-20 更新:
已經找到原因,關鍵點如下:
GCC把絕對位址調用(包括函數指標調用)編譯為BLX Rn指令。BX,BLX指令用於絕對跳轉(B,BL則用於跳轉),轉到Rn中包含的地址。總之,其中的X有兩個意思:(1)表示根據Rn的最低位是否為0,確定是否進行ARM/Thum切換。(2)可以絕對位址跳轉。
S3C2440A的指令集中沒有BLX,只有BX。故運行時會無效指令錯誤。
解決思路:
(1)給GCC適當的參數運行,來避免產生BLX指令。目前尚不清楚需要什麼參數選項。
(2)利用Undefined例外處理常式來類比BLX指令。這個看起來複雜,實現起來其實非常簡單,已經在v0.9中實現。