下面兩個源文件將解答這個問題,使問題容易明白。第一個源文件FPCALLER.C,包括一個函數,它通過一個函數指針(fptr)調用另一個函數。
voidfunc_caller(long (code *fptr) (unsigned int))
{
unsigned char i;
for(i=0;i<10;i++)
{
(*ftpr)(i);
}
}
第二個源文件FPMAIN.C,包含C主函數和被func_caller調用的函數func。注意main函數調用func_caller,把func的地址作為參數傳遞給func_caller。
extern void func_caller (long (code *) (unsigned int));
int func (unsigned int count)
{
long j;
long k;
k = 0;
for (j = 0; j < count; j++)
{
k += j;
}
return (k);
}
void main (void)
{
func_caller (func);
while (1) ;
}
上面的兩個的源文件編譯和鏈接都沒有錯誤。通過連接器,調用樹的映射文件如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
-------------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPMAIN
?PR?MAIN?FPMAIN----------
+--> ?PR?_FUNC?FPMAIN
+--> ?PR?_FUNC_CALLER?FPCALLER
?PR?_FUNC?FPMAIN0008H000AH
?PR?_FUNC_CALLER?FPCALLER0008H0003H
在這個簡單的例子中,許多信息可以從調用樹里挖掘出來。?C_C51STARTUP段調用main函數的?PR?MAIN?FPMAIN,段名各部分解析:PR是代碼存儲區,MAIN是函數名,FPMAIN是定義函數所在的源文件名。
MAIN函數調用FUNC和FUNC_CALLER(根據調用樹)。注意這是錯誤的。MAIN函數沒有調用FUNC函數,但是它傳遞FUNC函數的地址給FUNC_CALLER函數。同時注意,根據調用樹FUNC_CALLER沒有調用FUNC。這是因為FUNC_CALLER是通過函數指針間接調用FUNC。
FPMAIN文件中的FUNC函數使用從0008H開始,長000AH字節的數據。FPCALLER文件中的FUNC_CALLER函數也使用從0008H開始,長0003H字節的數據。這是重要的。
FUNC_CALLER函數使用的存儲區從0008H開始,FUNC函數使用的存儲區也是從0008H開始。因為FUNC_CALLER函數調用FUNC函數,又因為兩個函數使用相同的存儲區,這樣就產生了問題。當FUNC函數被FUNC_CALLER函數調用時,存儲區將被FUNC_CALLER破壞。這個問題是怎樣產生的?是由Keil 51編譯器產生還是由連接器產生?
這個問題的原因是函數指針。當你使用函數指針時,你將總是遇到這樣的問題。幸運的是,他們是容易被修改的。“OVERLAY”指令讓你指定在調用樹中,函數與其他函數是怎樣連接的。
為了修正上面顯示的調用樹,FUNC函數必須從MAIN函數中刪除,同時FUNC函數必須插入到FUNC_CALLER函數中。下面用“OVERLAY”指令修改后如下:
OVERLAY (?PR?MAIN?FPMAIN ~ ?PR?_FUNC?FPMAIN,
?PR?_FUNC_CALLER?FPCALLER ! ?PR?_FUNC?FPMAIN)
為了刪除或插入相關的進入調用樹,指定第一調用和第二調用。“~”符號用于刪除相關的函數,“!”用于插入一個外部函數。例如?PR?MAIN?FPMAIN ~ ?PR?_FUNC?FPMAIN,意義是從MAIN函數中刪除FUNC函數的調用。
經過調整連接命令,包括用“OVERLAY”指令修正調用樹,調整后的映射文件如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
-------------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPMAIN
?PR?MAIN?FPMAIN----------
+--> ?PR?_FUNC_CALLER?FPCALLER
?PR?_FUNC_CALLER?FPCALLER0008H0003H
+--> ?PR?_FUNC?FPMAIN
?PR?_FUNC?FPMAIN000BH000AH
修正后的調用樹中,FUNC_CALLER函數和FUNC函數使用獨立存儲空間。
函數指針列表
下面是一個典型的函數指針列表的定義:
long (code *fp_tab []) (void) = { func1, func2, func3 };
如果你的MAIN函數中通過fp_tab調用歌函數,連接映射文件出現如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
----------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPT_MAIN
+--> ?C_INITSEG
?PR?MAIN?FPT_MAIN0008H0001H
?C_INITSEG----------
+--> ?PR?FUNC1?FP_TAB
+--> ?PR?FUNC2?FP_TAB
+--> ?PR?FUNC3?FP_TAB
?PR?FUNC1?FP_TAB0008H0008H
?PR?FUNC2?FP_TAB0008H0008H
?PR?FUNC3?FP_TAB0008H0008H
三個函數通過列表被調用,FUNC1,FUNC2 和FUNC3被C_INITSEG調用。但是這是錯誤的,C_INITSEG按照常規的方式在程序中初始化。這些函數被引入初始化代碼中,因為函數指針列表被初始化成這些函數的地址值。
注意這些變量(FUNC1,FUNC2 和FUNC13)和MAIN函數的起始地址都是0008H。這樣不能正常工作,因為MAIN函數調用FUNC1,FUNC2 和FUNC3(通過函數指針類表)。
C51編譯器和BL51連接器聯合工作,當使用函數指針列表時,使得函數變量空間覆蓋很容易。但是,你必須合理的聲明指針列表。如果你這樣做了,就可以避免使用“OVERLAY”指令。下面的函數指針列表的定義,C51和BL51可以自動處理:
code long (code *fp_tab []) (void) = { func1, func2, func3 };
注意唯一不同的是存儲列表在CODE空間。現在,連接映射文件如下:
SEGMENTDATA_GROUP
+--> CALLED SEGMENTSTARTLENGTH
----------------------------------------------
?C_C51STARTUP----------
+--> ?PR?MAIN?FPT_MAIN
?PR?MAIN?FPT_MAIN0008H0001H
+--> ?CO?FP_TAB
?CO?FP_TAB----------
+--> ?PR?FUNC1?FP_TAB
+--> ?PR?FUNC2?FP_TAB
+--> ?PR?FUNC3?FP_TAB
?PR?FUNC1?FP_TAB0009H0008H
?PR?FUNC2?FP_TAB0009H0008H
?PR?FUNC3?FP_TAB0009H0008H
現在,初始化代碼中沒有引入FUNC1,FUNC2 和FUNC3。但是,MAIN函數中引入一個常數段FP_TAB。這是一個函數指針列表。因為函數指針列表引入了FUNC1,FUNC2 和FUNC3,所以調用樹是正確的。
只要把函數指針列表放在一個獨立的源文件中,在調用樹中,C51和BL51就能正確的連接。
評論