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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

函数的可重入和不可重入

發布時間:2023/12/20 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 函数的可重入和不可重入 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

https://www.ibm.com/developerworks/cn/linux/l-reent.html這是一篇描述重入函數和不可重入函數的文章。先把他copy過來:
在早期的編程中,不可重入性對程序員并不構成威脅;函數不會有并發訪問,也沒有中斷。在很多較老的 C 語言實現中,函數被認為是在單線程進程的環境中運行。
不過,現在,并發編程已普遍使用,您需要意識到這個缺陷。本文描述了在并行和并發程序設計中函數的不可重入性導致的一些潛在問題。信號的生成和處理尤其增加了額外的復雜性。由于信號在本質上是異步的,所以難以找出當信號處理函數 觸發某個不可重入函數時導致的 bug。

本文:
a. 定義了可重入性,并包含一個可重入函數的 POSIX 清單。
b. 給出了示例,以說明不可重入性所導致的問題。
c. 指出了確保底層函數的可重入性的方法。
d. 討論了在編譯器層次上對可重入性的處理。

什么是可重入性?
可重入(reentrant)函數可以由多于一個任務并發使用,而不必擔心數據錯誤。相反, 不可重入(non-reentrant)函數不能由超過一個任務所共享,除非能確保函數的互斥 (或者使用信號量,或者在代碼的關鍵部分禁用中斷)。可重入函數可以在任意時刻被中斷, 稍后再繼續運行,不會丟失數據。可重入函數要么使用本地變量,要么在使用全局變量時 保護自己的數據。
可重入函數:
a. 不為連續的調用持有靜態數據。
b. 不返回指向靜態數據的指針;所有數據都由函數的調用者提供。
c. 使用本地數據,或者通過制作全局數據的本地拷貝來保護全局數據。
d. 絕不調用任何不可重入函數。
不要混淆可重入與線程安全。在程序員看來,這是兩個獨立的概念:函數可以是可重入的,是線程安全的,或者 二者皆是,或者二者皆非。不可重入的函數不能由多個線程使用。另外,或許不可能讓某個 不可重入的函數是線程安全的。
IEEE Std 1003.1 列出了 118 個可重入的 UNIX? 函數,在此沒有給出副本。參見 參考資料 中指向 unix.org 上此列表的鏈接。
出于以下任意某個原因,其余函數是不可重入的:
a. 它們調用了 malloc 或 free。
b. 眾所周知它們使用了靜態數據結構體。
c. 它們是標準 I/O 程序庫的一部分。

信號和不可重入函數
信號(signal)是軟件中斷。它使得程序員可以處理異步事件。為了向進程發送一個信號, 內核在進程表條目的信號域中設置一個位,對應于收到的信號的類型。信號函數的 ANSI C 原型是:

void (*signal (int sigNum, void (*sigHandler)(int))) (int);

或者,另一種描述形式:

typedef void sigHandler(int); SigHandler *signal(int, sigHandler *);

當進程處理所捕獲的信號時,正在執行的正常指令序列就會被信號處理器臨時中斷。然后進程繼續執行, 但現在執行的是信號處理器中的指令。如果信號處理器返回,則進程繼續執行信號被捕獲時正在執行的 正常的指令序列。
現在,在信號處理器中您并不知道信號被捕獲時進程正在執行什么內容。如果當進程正在使用 malloc 在它的堆上分配額外的內存時,您通過信號處理器調用 malloc,那會怎樣?或者,調用了正在處理全局數據結構的某個函數,而 在信號處理器中又調用了同一個函數。如果是調用 malloc,則進程會 被嚴重破壞,因為 malloc 通常會為所有它所分配的區域維持一個鏈表,而它又 可能正在修改那個鏈表。
甚至可以在需要多個指令的 C 操作符開始和結束之間發送中斷。在程序員看來,指令可能似乎是原子的 (也就是說,不能被分割為更小的操作),但它可能實際上需要不止一個處理器指令才能完成操作。 例如,看這段 C 代碼:

temp += 1;

在 x86 處理器上,那個語句可能會被編譯為:

mov ax,[temp] inc ax mov [temp],ax

這顯然不是一個原子操作。
這個例子展示了在修改某個變量的過程中運行信號處理器可能會發生什么事情:

清單 1. 在修改某個變量的同時運行信號處理器 #include <signal.h> #include <stdio.h> struct two_int { int a, b; } data; void signal_handler(int signum) {printf ("%d, %d\n", data.a, data.b);alarm (1); } int main (void) {static struct two_int zeros = { 0, 0 }, ones = { 1, 1 };signal (SIGALRM, signal_handler);data = zeros;alarm (1);while (1){data = zeros;data = ones;} }

這個程序向 data 填充 0,1,0,1,一直交替進行。同時,alarm 信號 處理器每一秒打印一次當前內容(在處理器中調用 printf 是安全的,當信號發生時 它確實沒有在處理器外部被調用)。您預期這個程序會有怎樣的輸出?它應該打印 0,0 或者 1,1。但是實際的輸出 如下所示:

0, 0 1, 1 (Skipping some output...) 0, 1 1, 1 1, 0 1, 0 ...

在大部分機器上,在 data 中存儲一個新值都需要若干個指令,每次存儲一個字。 如果在這些指令期間發出信號,則處理器可能發現 data.a 為 0 而 data.b 為 1,或者反之。另一方面,如果我們運行代碼的機器能夠在一個 不可中斷的指令中存儲一個對象的值,那么處理器將永遠打印 0,0 或 1,1。
使用信號的另一個新增的困難是,只通過運行測試用例不能夠確保代碼沒有信號 bug。這一困難的原因在于 信號生成本質上異步的。

不可重入函數和靜態變量
假定信號處理器使用了不可重入的 gethostbyname。這個函數 將它的值返回到一個靜態對象中:

static struct hostent host; /* result stored here*/

它每次都重新使用同一個對象。在下面的例子中,如果信號剛好是在 main 中調用 gethostbyname 期間到達,或者甚至在調用之后到達,而程序仍然在使用那個值,則 它將破壞程序請求的值。

清單 2. gethostbyname 的危險用法 main() {struct hostent *hostPtr;...signal(SIGALRM, sig_handler);...hostPtr = gethostbyname(hostNameOne);... } void sig_handler() {struct hostent *hostPtr;.../* call to gethostbyname may clobber the value stored during the call inside the main() */hostPtr = gethostbyname(hostNameTwo);... }

不過,如果程序不使用 gethostbyname 或者任何其他在同一對象中返回信息 的函數,或者如果它每次使用時都會阻塞信號,那么就是安全的。
很多庫函數在固定的對象中返回值,總是使用同一對象,它們全都會導致相同的問題。如果某個函數使用并修改了 您提供的某個對象,那它可能就是不可重入的;如果兩個調用使用同一對象,那么它們會相互干擾。
當使用流(stream)進行 I/O 時會出現類似的情況。假定信號處理器使用 fprintf 打印一條消息,而當信號發出時程序正在使用同一個流進行 fprintf 調用。 信號處理器的消息和程序的數據都會被破壞,因為兩個調用操作了同一數據結構:流本身。
如果使用第三方程序庫,事情會變得更為復雜,因為您永遠不知道哪部分程序庫是可重入的,哪部分是不可重入的。 對標準程序庫而言,有很多程序庫函數在固定的對象中返回值,總是重復使用同一對象,這就使得那些函數 不可重入。
近來很多提供商已經開始提供標準 C 程序庫的可重入版本,這是一個好消息。對于任何給定程序庫,您都應該通讀它所提供 的文檔,以了解其原型和標準庫函數的用法是否有所變化。

確保可重入性的經驗
理解這五條最好的經驗將幫助您保持程序的可重入性。
經驗 1
返回指向靜態數據的指針可能會導致函數不可重入。例如,將字符串轉換為大寫的 strToUpper 函數可能被實現如下:

清單 3. strToUpper 的不可重入版本 char *strToUpper(char *str) {/*Returning pointer to static data makes it non-reentrant */static char buffer[STRING_SIZE_LIMIT];int index;for (index = 0; str[index]; index++)buffer[index] = toupper(str[index]);buffer[index] = '\0';return buffer; }

通過修改函數的原型,您可以實現這個函數的可重入版本。下面的清單為輸出準備了存儲空間:

清單 4. strToUpper 的可重入版本 char *strToUpper_r(char *in_str, char *out_str) {int index;for (index = 0; in_str[index] != '\0'; index++)out_str[index] = toupper(in_str[index]);out_str[index] = '\0';return out_str; }

由進行調用的函數準備輸出存儲空間確保了函數的可重入性。注意,這里遵循了標準慣例,通過向函數名添加“_r”后綴來 命名可重入函數。
經驗 2
記憶數據的狀態會使函數不可重入。不同的線程可能會先后調用那個函數,并且修改那些數據時不會通知其他 正在使用此數據的線程。如果函數需要在一系列調用期間維持某些數據的狀態,比如工作緩存或指針,那么 調用者應該提供此數據。
在下面的例子中,函數返回某個字符串的連續小寫字母。字符串只是在第一次調用時給出,如 strtok 子例程。當搜索到字符串末尾時,函數返回 \0。函數可能如下實現:

清單 5. getLowercaseChar 的不可重入版本 char getLowercaseChar(char *str) {static char *buffer;static int index;char c = '\0';/* stores the working string on first call only */if (string != NULL) {buffer = str;index = 0;}/* searches a lowercase character */while(c=buff[index]){if(islower(c)){index++;break;}index++;}return c; }

這個函數是不可重入的,因為它存儲變量的狀態。為了讓它可重入,靜態數據,即 index, 需要由調用者來維護。此函數的可重入版本可能類似如下實現:

清單 6. getLowercaseChar 的可重入版本 char getLowercaseChar_r(char *str, int *pIndex) {char c = '\0';/* no initialization - the caller should have done it *//* searches a lowercase character */while(c=buff[*pIndex]){if(islower(c)){(*pIndex)++; break;}(*pIndex)++;}return c; }

經驗 3
在大部分系統中,malloc 和 free 都不是可重入的, 因為它們使用靜態數據結構來記錄哪些內存塊是空閑的。實際上,任何分配或釋放內存的庫函數都是不可重入的。這也包括分配空間存儲結果的函數。
避免在處理器分配內存的最好方法是,為信號處理器預先分配要使用的內存。避免在處理器中釋放內存的最好方法是, 標記或記錄將要釋放的對象,讓程序不間斷地檢查是否有等待被釋放的內存。不過這必須要小心進行,因為將一個對象 添加到一個鏈并不是原子操作,如果它被另一個做同樣動作的信號處理器打斷,那么就會“丟失”一個對象。不過, 如果您知道當信號可能到達時,程序不可能使用處理器那個時刻所使用的流,那么就是安全的。如果程序使用的是某些其他流,那么也不會有任何問題。
經驗 4
為了編寫沒有 bug 的代碼,要特別小心處理進程范圍內的全局變量,如 errno 和 h_errno。 考慮下面的代碼:

清單 7. errno 的危險用法 if (close(fd) < 0) {fprintf(stderr, "Error in close, errno: %d", errno);exit(1); }

假定信號在 close 系統調用設置 errno 變量 到其返回之前這一極小的時間片段內生成。這個生成的信號可能會改變 errno 的值,程序的行為會無法預計。
如下,在信號處理器內保存和恢復 errno 的值,可以解決這一問題:

清單 8. 保存和恢復 errno 的值 void signalHandler(int signo) {int errno_saved;/* Save the error no. */errno_saved = errno;/* Let the signal handler complete its job */....../* Restore the errno*/errno = errno_saved; }

經驗 5
如果底層的函數處于關鍵部分,并且生成并處理信號,那么這可能會導致函數不可重入。通過使用信號設置和 信號掩碼,代碼的關鍵區域可以被保護起來不受一組特定信號的影響,如下:
保存當前信號設置。
用不必要的信號屏蔽信號設置。
使代碼的關鍵部分完成其工作。
最后,重置信號設置。
下面是此方法的概述:

清單 9. 使用信號設置和信號掩碼 sigset_t newmask, oldmask, zeromask; ... /* Register the signal handler */ signal(SIGALRM, sig_handler); /* Initialize the signal sets */ sigemtyset(&newmask); sigemtyset(&zeromask); /* Add the signal to the set */ sigaddset(&newmask, SIGALRM); /* Block SIGALRM and save current signal mask in set variable 'oldmask' */ sigprocmask(SIG_BLOCK, &newmask, &oldmask); /* The protected code goes here ... ... */ /* Now allow all signals and pause */ sigsuspend(&zeromask); /* Resume to the original signal mask */ sigprocmask(SIG_SETMASK, &oldmask, NULL); /* Continue with other parts of the code */

忽略 sigsuspend(&zeromask); 可能會引發問題。從消除信號阻塞到進程執行下一個 指令之間,必然會有時鐘周期間隙,任何在此時間窗口發生的信號都會丟掉。函數調用 sigsuspend 通過重置信號掩碼并使進程休眠一個單一的原子操作來解決這一問題。如果您能確保在此時間窗口中生成的信號不會有任何 負面影響,那么您可以忽略 sigsuspend 并直接重新設置信號。

在編譯器層次處理可重用性
我將提出一個在編譯器層次處理可重入函數的模型。可以為高級語言引入一個新的關鍵字: reentrant,函數可以被指定一個 reentrant 標識符,以此確保函數可重入,比如:

reentrant int foo();

此指示符告知編譯器要專門處理那個特殊的函數。編譯器可以將這個指示符存儲在它的符號表中,并在中間代碼生成階段 使用這個指示符。為達到此目的,編譯器的前端設計需要有一些改變。此可重入指示符遵循這些準則:
不為連續的調用持有靜態數據。
通過制作全局數據的本地拷貝來保護全局數據。
絕對不調用不可重入的函數。
不返回對靜態數據的引用,所有數據都由函數的調用者提供。
準則 1 可以通過類型檢查得到保證,如果在函數中有任何靜態存儲聲明,則拋出錯誤消息。這可以在編譯的語法分析 階段完成。
準則 2,全局數據的保護可以通過兩種方式得到保證。基本的方法是,如果函數修改全局數據,則拋出一個錯誤 消息。一種更為復雜的技術是以全局數據不被破壞的方式生成中間代碼。可以在編譯器層實現類似于前面經驗 4 的方法。 在進入函數時,編譯器可以使用編譯器生成的臨時名稱存儲將要被操作的全局數據,然后在退出函數時恢復那些數據。 使用編譯器生成的臨時名稱存儲數據對編譯器來說是常用的方法。
確保準則 3 得到滿足,要求編譯器預先知道所有可重入函數,包括應用程序所使用的程序庫。這些關于函數的 附加信息可以存儲在符號表中。
最后,準則 4 已經得到了準則 2 的保證。如果函數沒有靜態數據,那么也就不存在返回靜態數據的引用的問題。
提出的這個模型將簡化程序員遵循可重入函數準則的工作,而且使用此模型可以預防代碼出現無意的可重入性 bug。


下面就我理解,簡單描述可重入和不可重入的函數。

在多線程環境中,可重入函數與不可重入函數對線程安全至關重要。一個可重入的函數簡單來說就是可以被中斷的函數,也就是說可以在該函數的任意時刻中斷它,通過系統調度去執行另一段代碼或者本函數片段被重新執行,返回時執行原先的函數的數據不會被破壞或者改變。不可重入函數則相反,一旦被中斷執行,返回執行時候數據可能會被破壞或者改變。

在Linux API(庫函數 / 系統調用)中,重入與不可重入的函數隨處可見,如:

時間類編程: char *asctime(const struct tm *tm); char *asctime_r(const struct tm *tm, char *buf);char *ctime(const time_t *timep); char *ctime_r(const time_t *timep, char *buf);struct tm *gmtime(const time_t *timep); struct tm *gmtime_r(const time_t *timep, struct tm *result);struct tm *localtime(const time_t *timep); struct tm *localtime_r(const time_t *timep, struct tm *result);目錄類編程: struct dirent *readdir(DIR *dirp); int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);字符串類編程: char *strtok(char *str, const char *delim); char *strtok_r(char *str, const char *delim, char **saveptr);

只要函數名含”_r”,那么它就是可重入函數,與之對應的沒有”_r”則是不可重入函數。
下面以一個簡單的例子,表現重入與不可重入的函數關系,及其使用。

代碼功能: 可重入遍歷數組 / 不可重入遍歷數組。

#include <stdio.h> #include <stdlib.h> #define MAX 6int i; //數組的索引值//可重入,它并不依賴全局索引值,而是操作用aip調用者分配來的空間,確保函數是可以重入的 int traverse_arr_r(int *arr, int **save) {if (**save + 1 < MAX && **save >= 0)return arr[(**save)++];return -1; }//不可重入,依賴全局索引值 int traverse_arr(int *arr) {if (i + 1 < MAX)return arr[i++];return -1; }int main(void) {int a[5] = {1, 2, 3, 4, 5};int num;//int i = 0;int *i = NULL;i = (int* )malloc(sizeof(int));//調用不可重入的函數,還需要注意遍歷完一次后要讓全局索引歸零//while ((num = traverse_arr(a)) != -1)//{// printf("num = %d\n", num);//}//調用可重入的函數while ((num = traverse_arr_r(a, &i)) != -1){printf("num = %d\n", num);}free(i);return 0; }

總結

以上是生活随笔為你收集整理的函数的可重入和不可重入的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 国产久草视频 | 久久伊人一区二区 | 16—17女人毛片 | 久久国产剧情 | 欧美日韩在线视频免费观看 | 狠狠操狠狠操 | 国产一区二区三区视频 | 久久精品一区二区免费播放 | 狠狠躁狠狠躁视频专区 | 香蕉尹人 | 91涩涩涩 | 成人 黄 色 免费播放 | 亚洲欧美日韩激情 | 亚洲AV无码国产精品国产剧情 | 欧美一级片在线视频 | 一级片在线免费 | 黄大色黄大片女爽一次 | 99久久99久久久精品棕色圆 | 国产精品白浆一区二小说 | 中文字幕一区二区三区乱码在线 | 一级国产片 | 日韩激情视频 | 国产一区二区在线播放 | 伊人成人在线 | 丰满秘书被猛烈进入高清播放在 | 久久婷婷综合国产 | 91美女视频网站 | 黄色激情视频在线观看 | 爱视频福利网 | 熟女国产精品一区二区三 | 久久久久久久中文字幕 | 污视频网站免费在线观看 | 大奶子av| 日本大胆欧美人术艺术 | 免费毛片大全 | 男女性生活毛片 | 97超级碰碰 | 国产suv精品一区二区三区 | 青青草中文字幕 | 美景之屋电影免费高清完整韩剧 | 一色屋免费视频 | a级性生活视频 | 欧美精品自拍 | 欧美性网站 | 依人久久 | 久久1024| 亚洲AV成人午夜无码精品久久 | 91黄在线观看| 少妇aa | 巨胸喷奶水www久久久免费动漫 | 偷拍超碰 | 麻豆av网 | 日本一区二区三区免费在线观看 | 黑人巨大猛烈捣出白浆 | 九九视频在线播放 | 国产伦理片在线观看 | 麻豆av一区二区三区在线观看 | 久久视频国产 | 日日夜夜噜 | 17c一起操 | 一区二区三区四区五区av | 无码精品人妻一区二区三区影院 | 国产传媒一区二区三区 | 中文字幕一区二区人妻在线不卡 | 久久黄色av | 久久11| 手机看片日韩欧美 | 国产欧美一区二区三区精华液好吗 | 人妻体体内射精一区二区 | 麻豆啪啪| 轮番上阵免费观看在线电影 | 国产精品主播视频 | 玖玖视频国产 | 国产无遮挡aaa片爽爽 | 99色图 | 秋霞视频在线观看 | 老司机午夜福利视频 | 日本韩国欧美一区二区 | 日韩欧美三级在线观看 | 国产亚韩 | 久久欲| 国产精品久久久久久久av福利 | 欧美成人一区在线 | 麻豆视频软件 | 琪琪色视频 | 国产免费看片 | 就爱操av | 日韩三区视频 | 免费在线视频一区 | 日本少妇喂奶 | 男女透逼视频 | 大又大又粗又硬又爽少妇毛片 | 久久精品综合网 | 日本一区二区高清视频 | 日韩国产网站 | 动漫美女被x | 国产色婷婷一区二区三区竹菊影视 | 婷婷影音 | 五月婷婷在线观看视频 |