日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

        歡迎訪問(wèn) 生活随笔!

        生活随笔

        當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

        编程问答

        函数的可重入性

        發(fā)布時(shí)間:2025/1/21 编程问答 34 豆豆
        生活随笔 收集整理的這篇文章主要介紹了 函数的可重入性 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

        1.什么是可重入性

        重入一般可以理解為一個(gè)函數(shù)在同時(shí)多次調(diào)用,例如操作系統(tǒng)在進(jìn)程調(diào)度過(guò)程中,或者單片機(jī)、處理器等的中斷的時(shí)候會(huì)發(fā)生重入的現(xiàn)象。

        可重入的函數(shù)必須滿足以下三個(gè)條件:

        (1)可以在執(zhí)行的過(guò)程中可以被打斷;

        (2)被打斷之后,在該函數(shù)一次調(diào)用執(zhí)行完之前,可以再次被調(diào)用(或進(jìn)入,reentered)。

        (3)再次調(diào)用執(zhí)行完之后,被打斷的上次調(diào)用可以繼續(xù)恢復(fù)執(zhí)行,并正確執(zhí)行。

        可重入函數(shù)可以在任意時(shí)刻被中斷,稍后再繼續(xù)運(yùn)行,不會(huì)丟失數(shù)據(jù)。不可重入(non-reentrant)函數(shù)不能由超過(guò)一個(gè)任務(wù)所共享,除非能確保函數(shù)的互斥(或者使用信號(hào)量,或者在代碼的關(guān)鍵部分禁用中斷)。

        通常,以下幾種情況會(huì)受到可重入性的制約:

        (1)信號(hào)處理程序A內(nèi)外都調(diào)用了同一個(gè)不可重入函數(shù)B;B在執(zhí)行期間被信號(hào)打斷,進(jìn)入A (A中調(diào)用了B),完事之后返回B被中斷點(diǎn)繼續(xù)執(zhí)行,這時(shí)B函數(shù)的環(huán)境可能改變,其結(jié)果就不可預(yù)料了。
        眾所周知,在進(jìn)程中斷期間,系統(tǒng)會(huì)保存和恢復(fù)進(jìn)程的上下文,然而恢復(fù)的上下文僅限于返回地址,cpu寄存器等之類(lèi)的少量上下文,而函數(shù)內(nèi)部使用的諸如全局靜態(tài)變量buffer等并不在保護(hù)之列,所以如果這些值在函數(shù)被中斷期間發(fā)生了改變,那么當(dāng)函數(shù)回到斷點(diǎn)繼續(xù)執(zhí)行時(shí),其結(jié)果就不可預(yù)料了。打個(gè)比方,比如malloc,將如一個(gè)進(jìn)程此時(shí)正在執(zhí)行malloc分配堆空間,此時(shí)程序捕捉到信號(hào)發(fā)生中斷,執(zhí)行信號(hào)處理程序中恰好也有一個(gè)malloc,這樣就會(huì)對(duì)進(jìn)程的環(huán)境造成破壞,因?yàn)閙alloc通常為它所分配的存儲(chǔ)區(qū)維護(hù)一個(gè)鏈接表,插入執(zhí)行信號(hào)處理函數(shù)時(shí),進(jìn)程可能正在對(duì)這張表進(jìn)行操作,而信號(hào)處理函數(shù)的調(diào)用剛好覆蓋了進(jìn)程的操作,造成錯(cuò)誤

        (2)多線程共享進(jìn)程內(nèi)部的資源,如果兩個(gè)線程A,B調(diào)用同一個(gè)不可重入函數(shù)F,A線程進(jìn)入F后,線程調(diào)度,切換到B,B也執(zhí)行了F,那么當(dāng)再次切換到線程A時(shí),其調(diào)用F的結(jié)果也是不可預(yù)料的。


        常見(jiàn)的不可重入函數(shù)有:
        printf --------引用全局變量stdout
        malloc --------全局內(nèi)存分配表
        free??? --------全局內(nèi)存分配表

        2.可重入與線程安全

        可重入的定義源于單線程環(huán)境。在單線程環(huán)境下,一段代碼在執(zhí)行中可能被硬件中斷,并轉(zhuǎn)而調(diào)用中斷服務(wù)程序(ISR)。在本次調(diào)用中斷處理函數(shù)之前,有可能中斷處理函數(shù)已經(jīng)在執(zhí)行。因此,任何中斷處理函數(shù)都應(yīng)該是可重入的。

        線程安全的概念則源自于多線程環(huán)境。可見(jiàn),他們的起源是不一樣的。那么,他們沒(méi)有什么必然關(guān)系呢。可總結(jié)如下:

        (1)一個(gè)線程安全的函數(shù)不一定是可重入的;

        2)一個(gè)可重入的函數(shù)缺也不一定是線程安全的!

        3.不可重入的危害

        在單線程進(jìn)程中,只存在一個(gè)控制流。因此,這些進(jìn)程所執(zhí)行的代碼無(wú)需重入或是線程安全的。在多線程程序中,相同的功能和資源可以通過(guò)多個(gè)控制流并發(fā)訪問(wèn)。

        要保護(hù)資源的完整性,編寫(xiě)的多線程程序代碼必須能重入并是線程安全的。不可重入對(duì)多線程環(huán)境的危害是很大的,甚至?xí)斐上到y(tǒng)崩潰。

        4.不可重入的例子

        4.1 不可重入且線程不安全

        下面這個(gè)swap函數(shù)是不可重入的:

        [cpp]?view plain?copy ? ?
      1. int?t;??
      2. ???
      3. void?swap(int?*x,?int?*y)??
      4. {??
      5. ????t?=?*x;??
      6. ????*x?=?*y;??
      7. ???
      8. ????//?hardware?interrupt?might?invoke?isr()?here!??
      9. ????*y?=?t;??
      10. }??
      11. ???
      12. void?isr()??
      13. {??
      14. ????int?x?=?1,?y?=?2;??
      15. ????swap(&x,?&y);??
      16. }??
      17. 當(dāng)然了,跟據(jù)前面的總結(jié),既然它不可重入則它一定不是線程安全的。

        可以把t改成線程局部變量,使得該函數(shù)變成線程安全。然而,這樣修改的話,swap函數(shù)依然是不可重入的。例如一個(gè)線程已經(jīng)在執(zhí)行swap函數(shù),這個(gè)時(shí)候在同樣的語(yǔ)境下收到硬件中斷,isr()函數(shù)會(huì)被調(diào)用,進(jìn)而調(diào)用swap,swap的不可重入問(wèn)題就暴露出來(lái)了。

        4.2 可重入但是線程安全

        我們做一定修改,在swap函數(shù)里,在交換前,對(duì)此時(shí)刻的t全局變量做一個(gè)本地的緩存,在交換結(jié)束的時(shí)候,始終使用該緩存。這樣的話,swap函數(shù)在退出的時(shí)候,全局變量的之跟進(jìn)入的時(shí)候是一樣的。這樣,就可以保證該函數(shù)是線程可重入的。代碼如下:

        [cpp]?view plain?copy ? ?
      18. int?t;??
      19. ???
      20. void?swap(int?*x,?int?*y)??
      21. {??
      22. ????int?s;??
      23. ???
      24. ??<span?style="color:#FF0000;">??s?=?t;?//?save?global?variable</span>??
      25. ????t?=?*x;??
      26. ????*x?=?*y;??
      27. ???
      28. ????//?hardware?interrupt?might?invoke?isr()?here!??
      29. ????*y?=?t;??
      30. <span?style="color:#FF0000;">????t?=?s;?//?restore?global?variable</span>??
      31. }??
      32. ???
      33. void?isr()??
      34. {??
      35. ????int?x?=?1,?y?=?2;??
      36. ????swap(&x,?&y);??
      37. }??
      38. 然而,該函數(shù)在多線程環(huán)境下,依然是線程不安全的,因?yàn)樗鼰o(wú)法保證全局變量t的一致性。

        5.預(yù)防不可重入的幾個(gè)原則

        原則總結(jié)如下:

        (1)不要使用static變量和全局變量,堅(jiān)持只用局部變量;

        (2)若必須訪問(wèn)全局變量,利用互斥信號(hào)量來(lái)保護(hù)全局變量;

        (3)獲取得知哪些系統(tǒng)調(diào)用是可重入的,在多任務(wù)處理程序中都使用安全的系統(tǒng)調(diào)用;

        (4)不調(diào)用其它任何不可重入的函數(shù);

        (5)謹(jǐn)慎使用堆棧malloc/new。

        6.優(yōu)秀實(shí)踐:如何優(yōu)化已有代碼,使函數(shù)成為可重入?

        在多數(shù)情況下,必須用帶有已修改的將要重入的函數(shù)來(lái)替代非重入函數(shù)。非重入函數(shù)不能由多個(gè)線程使用。此外,可能也無(wú)法使非重入函數(shù)變?yōu)榫€程安全。

        6.1 返回指向靜態(tài)數(shù)據(jù)的指針的函數(shù)是不可重入的,如何使其變得可重入?


        許多非重入函數(shù)會(huì)返回一個(gè)指向靜態(tài)數(shù)據(jù)的指針。可以用以下方法來(lái)避免這種情況:
        • 返回動(dòng)態(tài)分配的數(shù)據(jù)。在這種情況下,調(diào)用程序?qū)⒇?fù)責(zé)釋放存儲(chǔ)量。好處在于無(wú)需對(duì)接口進(jìn)行修改。但是,向后兼容性就無(wú)法保證了;現(xiàn)有的使用已修改函數(shù)的單線程程序在不更改的情況下不會(huì)釋放存儲(chǔ)量,這將導(dǎo)致內(nèi)存泄漏。
        • 使用調(diào)用程序提供的存儲(chǔ)量。雖然必須修改接口,但是推薦使用這種方法。
        例如,將字符串轉(zhuǎn)換為大寫(xiě)的?strtoupper?函數(shù)可以用如以下所示的代碼段來(lái)實(shí)現(xiàn):/* 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] = 0return buffer; } 該函數(shù)不是重入函數(shù)(也不是線程安全的函數(shù))。要通過(guò)返回動(dòng)態(tài)分配的數(shù)據(jù)來(lái)使該函數(shù)重入,那么該函數(shù)應(yīng)類(lèi)似于以下代碼段:/* 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] = 0return buffer; } 較好的解決方案是修改接口。調(diào)用程序必須為輸入和輸出字符串提供存儲(chǔ)量,如以下代碼段所示:/* 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] = 0return out_str; }

        使用調(diào)用程序提供的存儲(chǔ)量使非重入標(biāo)準(zhǔn) C 庫(kù)子例程重入。

        6.2 在連續(xù)調(diào)用中保存數(shù)據(jù)的函數(shù)是不可重入的,如何修改?

        在連續(xù)調(diào)用中將不保存任何數(shù)據(jù),因?yàn)椴煌木€程可能連續(xù)地調(diào)用該函數(shù)。如果函數(shù)必須在連續(xù)調(diào)用中保存某些數(shù)據(jù),比如工作緩存或指針,那么調(diào)用程序應(yīng)提供該數(shù)據(jù)。

        請(qǐng)考慮以下例子。函數(shù)返回了字符串中連續(xù)的小寫(xiě)字符。該字符串只在第一次調(diào)用時(shí)提供,就像?strtok?子例程。函數(shù)在到達(dá)字符串的結(jié)尾處時(shí)返回 0。該函數(shù)可通過(guò)以下代碼段來(lái)實(shí)現(xiàn):/* 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; }


        該函數(shù)不是重入函數(shù)。要使其變?yōu)橹厝牒瘮?shù),那么調(diào)用程序必須保存靜態(tài)數(shù)據(jù)和變量?index。該函數(shù)的重入版本可通過(guò)以下代碼段來(lái)實(shí)現(xiàn):/* 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; } 該函數(shù)的接口和用法都發(fā)生了改變。調(diào)用程序必須向每次調(diào)用提供該字符串,且在首次調(diào)用前,必須將索引初始化為 0,如以下代碼所示:char *my_string; char my_char; int my_index; ... my_index = 0; while (my_char = reentrant_lowercase_c(my_string, &my_index)) {... }

        總結(jié)

        以上是生活随笔為你收集整理的函数的可重入性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

        如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。