函數可重入性

什麼是可重入性

重入一般可以理解爲一個函數在同時多次調用,例如操作系統在進程調度過程中,或者單片機、處理器等的中斷的時候會發生重入的現象。

可重入的函數必須滿足以下三個條件:

(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)) {
        ...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章