什麼是可重入性
重入一般可以理解爲一個函數在同時多次調用,例如操作系統在進程調度過程中,或者單片機、處理器等的中斷的時候會發生重入的現象。
可重入的函數必須滿足以下三個條件:
(1)可以在執行的過程中可以被打斷;
(2)被打斷之後,在該函數一次調用執行完之前,可以再次被調用(或進入,reentered)。
(3)再次調用執行完之後,被打斷的上次調用可以繼續恢復執行,並正確執行。
通常,以下幾種情況會受到可重入性的制約:
(1)信號處理程序A內外都調用了同一個不可重入函數B;B在執行期間被信號打斷,進入A (A中調用了B),完事之後返回B被中斷點繼續執行,這時B函數的環境可能改變,其結果就不可預料了。
衆所周知,在進程中斷期間,系統會保存和恢復進程的上下文,然而恢復的上下文僅限於返回地址,cpu寄存器等之類的少量上下文,而函數內部使用的諸如全局或靜態變量,buffer等並不在保護之列,所以如果這些值在函數被中斷期間發生了改變,那麼當函數回到斷點繼續執行時,其結果就不可預料了。打個比方,比如malloc,將如一個進程此時正在執行malloc分配堆空間,此時程序捕捉到信號發生中斷,執行信號處理程序中恰好也有一個malloc,這樣就會對進程的環境造成破壞,因爲malloc通常爲它所分配的存儲區維護一個鏈接表,插入執行信號處理函數時,進程可能正在對這張表進行操作,而信號處理函數的調用剛好覆蓋了進程的操作,造成錯誤。
(2)多線程共享進程內部的資源,如果兩個線程A,B調用同一個不可重入函數F,A線程進入F後,線程調度,切換到B,B也執行了F,那麼當再次切換到線程A時,其調用F的結果也是不可預料的。
常見的不可重入函數有:
printf ——–引用全局變量stdout
malloc ——–全局內存分配表
free ——–全局內存分配表
預防不可重入的幾個原則
原則總結如下:
(1)不要使用static變量和全局變量,堅持只用局部變量;
(2)若必須訪問全局變量,利用互斥信號量來保護全局變量;
(3)獲取得知哪些系統調用是可重入的,在多任務處理程序中都使用安全的系統調用;
(4)不調用其它任何不可重入的函數;
(5)謹慎使用堆棧malloc/new。
返回指向靜態數據的指針的函數
例如,將字符串轉換爲大寫的 strtoupper 函數可以用如以下所示的代碼段來實現:
/* non-reentrant function */
char *strtoupper(char *string)
{
static char buffer[MAX_STRING_SIZE];
int index;
for (index = 0; string[index]; index++)
buffer[index] = toupper(string[index]);
buffer[index] = 0
return buffer;
}
該函數不是重入函數(也不是線程安全的函數)。要通過返回動態分配的數據來使該函數重入,那麼該函數應類似於以下代碼段:
/* reentrant function (a poor solution) */
char *strtoupper(char *string)
{
char *buffer;
int index;
/* error-checking should be performed! */
buffer = malloc(MAX_STRING_SIZE);
for (index = 0; string[index]; index++)
buffer[index] = toupper(string[index]);
buffer[index] = 0
return buffer;
}
較好的解決方案是修改接口。調用程序必須爲輸入和輸出字符串提供存儲量,如以下代碼段所示:
/* reentrant function (a better solution) */
char *strtoupper_r(char *in_str, char *out_str)
{
int index;
for (index = 0; in_str[index]; index++)
out_str[index] = toupper(in_str[index]);
out_str[index] = 0
return out_str;
}
連續調用中保存數據的函數
請考慮以下例子。函數返回了字符串中連續的小寫字符。該字符串只在第一次調用時提供,就像 strtok 子例程。函數在到達字符串的結尾處時返回 0。該函數可通過以下代碼段來實現:
/* non-reentrant function */
char lowercase_c(char *string)
{
static char *buffer;
static int index;
char c = 0;
/* stores the string on first call */
if (string != NULL) {
buffer = string;
index = 0;
}
/* searches a lowercase character */
for (; c = buffer[index]; index++) {
if (islower(c)) {
index++;
break;
}
}
return c;
}
該函數不是重入函數。要使其變爲重入函數,那麼調用程序必須保存靜態數據和變量 index。該函數的重入版本可通過以下代碼段來實現:
/* reentrant function */
char reentrant_lowercase_c(char *string, int *p_index)
{
char c = 0;
/* no initialization - the caller should have done it */
/* searches a lowercase character */
for (; c = string[*p_index]; (*p_index)++) {
if (islower(c)) {
(*p_index)++;
break;
}
}
return c;
}
該函數的接口和用法都發生了改變。調用程序必須向每次調用提供該字符串,且在首次調用前,必須將索引初始化爲 0,如以下代碼所示:
char *my_string;
char my_char;
int my_index;
...
my_index = 0;
while (my_char = reentrant_lowercase_c(my_string, &my_index)) {
...
}