鎖機制是多線程編程中最常用的同步機制,用來對多線程間共享的臨界區進行保護。
1. 互斥鎖:pthread_mutex,屬于sleep-waiting類型的鎖
pthread_mutex_t *mutex;
int pthread_mutex_int(mutex, attr)? ? //以動態方式創建互斥鎖,參數attr指定了新建互斥鎖mutex的屬性
int pthread_mutex_lock(mutex) ? //加鎖
int pthread_mutex_unlock(mutex)? //解鎖
locak_irq_disable()、local_irq_enable()、local_irq_save()和local_irq_restore()為中斷處理函數,主要是在要進入臨界時禁止中斷加在出臨界區時使能中斷。
locak_irq_disable()、local_irq_enable()配對使用,而local_irq_save()和local_irq_restore()配對使用。
locak_irq_disable()和local_irq_save()都可以禁止中斷,但不同的是后者可以保存中斷狀態
local_irq_restore()在使能中斷的同時還恢復了由local_irq_save()所保存的中斷狀態
2. 自旋鎖:pin lock,屬于busy-wait類型的鎖
自旋鎖與互斥鎖的區別:自旋鎖不會引起調用者睡眠,如果自旋鎖已經被另的執行單元保持,調用者就一直循環在那里,看是否該自旋鎖的保持者已經釋放了鎖,“自旋”一詞而就是因此而得名。其作用是為了解決某項資源的互斥使用。因為自旋鎖不會引起調用者睡眠,所以自旋鎖的效率遠高于互斥鎖。
自旋鎖的不足之處:
(1) 自旋鎖一直占用CPU,它在未獲得鎖的情況下,一直運行-自旋,所以占用著CPU,如果不能在很短的時間內獲得鎖,這無疑會使CPU效率降低
(2) 在用自旋鎖時有可能造成死鎖,當遞歸調用時有可能造成死鎖,調用有些其他函數時也可能造成死鎖,如copy_to_user()、copy_from_user()、kmalloc()等
因此要慎用自旋鎖,自旋鎖有在內核可搶占式或SMP的情況下才真正需要。
關于pthread_mutex_t的初始化
class MyLock
{
private:
??? pthread_mutex_t m_lock;
public:
??? MyLock()
??? {
//????? m_lock = PTHREAD_MUTEX_INITIALIZER; //使用這種方法初始化時GCC編譯無法通過
??????? pthread_mutex_init(&m_lock,NULL);
??? }
??? ~MyLock()
??? {
??? }
??? void Lock()
??? {
??????? pthread_mutex_lock(&m_lock);
??? }
??? void unLock()
??? {
??????? pthread_mutex_unlock(&m_lock);
??? }
};
Unix網絡編程卷一有講:在靜態分配時,比如全局變量,我們必須將它初始化為PTHREAD_MUTEX_INITIALIZER,而如果在共享內存中分配時,必須用pthread_mutex_init來初始化。
在pthread.h頭文件中PTHREAD_MUTEX_INITIALIZER是這樣定義的:
# define PTHREAD_MUTEX_INITIALIZER \
? { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }
如果不對其進行初始化,對于某些系統來說如Solaris,靜態分配就是初始化為零,所以沒什么問題。但并非所有的系統都是這樣的,如Digtal Unix將初始化常值定義為非0。
Linux/Unix編程中的線程安全問題
在目前的計算機科學中,線程是操作系統調度的最小單元,進程是資源分配的最小單元。在大多數操作系統中,一個進程可以同時派生出多個線程。這些線程獨立執行,共享進程的資源。在單處理器系統中,多線程通過分時復用技術來技術,處理器在不同的線程間切換,從而更高效地利用系統 CPU資源。在多處理器和多核系統中,線程實際上可以同時運行,每個處理器或者核可以運行一個線程,系統的運算能力相對于單線程或者單進程大幅增強。
多線程技術讓多個處理器機器,多核機器和集群系統運行更快。因為多線程模型與生俱來的優勢可以使這些機器或者系統實現真實地的并發執行。但多線程在帶來便利的同時,也引入一些問題。線程主要由控制流程和資源使用兩部分構成,因此一個不得不面對的問題就是對共享資源的訪問。為了確保資源得到正確的使用,開發人員在設計編寫程序時需要考慮避免競爭條件和死鎖,需要更多地考慮使用線程互斥變量。
線程安全 (Thread-safe) 的函數就是一個在代碼層面解決上述問題比較好的方法,也成為多線程編程中的一個關鍵技術。如果在多線程并發執行的情況下,一個函數可以安全地被多個線程并發調用,可以說這個函數是線程安全的。反之,則稱之為“非線程安全”函數。注意:在單線程環境下,沒有“線程安全”和“非線程安全”的概念。因此,一個線程安全的函數允許任意地被任意的線程調用,程序開發人員可以把主要的精力在自己的程序邏輯上,在調用時不需要考慮鎖和資源訪問控制,這在很大程度上會降低軟件的死鎖故障和資源并發訪問沖突的機率。所以,開發人員應盡可能編寫和調用線程安全函數。
?
判斷一個函數是否線程安全不是一件很容易的事情。但是讀者可以通過下面這幾條確定一個函數是線程不安全的。
- a, 函數中訪問全局變量和堆。
- b, 函數中分配,重新分配釋放全局資源。
- c, 函數中通過句柄和指針的不直接訪問。
- d, 函數中使用了其他線程不安全的函數或者變量。
?
因此在編寫線程安全函數時,要注意兩點:
- 1, 減少對臨界資源的依賴,盡量避免訪問全局變量,靜態變量或其它共享資源,如果必須要使用共享資源,所有使用到的地方必須要進行互斥鎖 (Mutex) 保護;
- 2, 線程安全的函數所調用到的函數也應該是線程安全的,如果所調用的函數不是線程安全的,那么這些函數也必須被互斥鎖 (Mutex) 保護;
?
舉個例子(參考 例子 1),下面的這個函數 sum()是線程安全的,因為函數不依賴任何全局變量。
?int sum(int i, int j) {?
? ? ? return (i+j);?
?}?
但如果按下面的方法修改,sum()就不再是線程安全的,因為它調用的函數 inc_sum_counter()不是線程安全的,該函數訪問了未加鎖保護的全局變量 sum_invoke_counter。這樣的代碼在單線程環境下不會有任何問題,但如果調用者是在多線程環境中,因為 sum()有可能被并發調用,所以全局變量 sum_invoke_counter很有可能被并發修改,從而導致計數出錯。
?static int sum_invoke_counter = 0;?
?void inc_sum_counter(int i, int j) {?
?? ? sum_invoke_counter++;?
?}?
? ?
?int sum(int i, int j) {?
? ? inc_sum_counter();?
? ? return (i+j);?
?}?
我們可通過對全局變量 sum_invoke_counter添加鎖保護,使得 inc_sum_counter()成為一個線程安全的函數。
?static int sum_invoke_counter = 0;?
?static pthread_mutex_t sum_invoke_counter_lock = PTHREAD_MUTEX_INITIALIZER;?
?void inc_sum_counter(int i, int j) {?
? ? pthread_mutex_lock( &sum_invoke_counter_lock );?
? ? sum_invoke_counter++;?
? ? pthread_mutex_unlock( &sum_invoke_counter_lock );?
?}?
? ?
?int sum(int i, int j) {?
? ? inc_sum_counter();?
? ? return (i+j);?
?}?
現在 , sum()和 inc_sum_counter()都成為了線程安全函數。在多線程環境下,sum()可以被并發的調用,但所有訪問inc_sum_counter()線程都會在互斥鎖 sum_invoke_counter_lock上排隊,任何一個時刻都只允許一個線程修改sum_invoke_counter,所以 inc_sum_counter()就是現成安全的。
除了線程安全還有一個很重要的概念就是 可重入(Re-entrant),所謂可重入,即:當一個函數在被一個線程調用時,可以允許被其他線程再調用。顯而易見,如果一個函數是可重入的,那么它肯定是線程安全的。但反之未然,一個函數是線程安全的,卻未必是可重入的。程序開發人員應該盡量編寫可重入的函數。
一個函數想要成為可重入的函數,必須滿足下列要求:
- a) 不能使用靜態或者全局的非常量數據
- b) 不能夠返回地址給靜態或者全局的非常量數據
- c) 函數使用的數據由調用者提供
- d) 不能夠依賴于單一資源的鎖
- e) 不能夠調用非可重入的函數
?
對比前面的要求,例子 1的 sum()函數是可重入的,因此也是線程安全的。例子 3中的 inc_sum_counter()函數雖然是線程安全的,但是由于使用了靜態變量和鎖,所以它是不可重入的。因為 例子 3中的 sum()使用了不可重入函數inc_sum_counter(), 它也是不可重入的。
?
如果把一個非線程安全的函數作為線程安全對待,那么結果可能是無法預料的,例如 下面這段代碼是對 basename()的錯誤用法:
?#include <unistd.h>?
?#include <stdio.h>?
?#include <stdarg.h>?
?#include <pthread.h>?
?#include <string.h>?
?#include <libgen.h>?
?void printf_sa(char *fmt, ...) {?
? ? static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;?
? ? va_list args;?
? ? va_start(args, fmt);?
? ? pthread_mutex_lock(&lock);?
? ? vprintf(fmt, args);?
? ? pthread_mutex_unlock(&lock);?
? ? va_end(args);?
?}?
?void* basename_test(void *arg) {?
? ? pthread_t self = pthread_self();?
? ? char *base = basename((char*)arg);?
? ? printf_sa("TI-%u: base: %s/n", self, base);?
?}?
?int main(int argc, char *argv) {?
? ? int i = 0;?
? ? pthread_t tids[2];?
? ? char msg[1024];?
? ? strcpy(msg, "/tmp/test");?
? ? pthread_create(&tids[0], NULL, basename_test, msg);?
? ? msg[7] -= 32;?
? ? pthread_create(&tids[1], NULL, basename_test, msg);?
? ? pthread_join(tids[0], NULL);?
? ? pthread_join(tids[1], NULL);?
? ? return 0;?
?}?
這段代碼的意思是在兩個并發的線程中同時執行函數 basename(),然后打印出對于路徑 "/tmp/test"和"/tmp/teSt"的結果。
編譯 ( 注意:如編譯器提示 pthread_create函數不能找到可能需要連接庫 pthread,請需添加 -lpthread選項 ) 執行這段代碼,你會發現大部分情況下屏幕上都會打印類似出如下結果 :
?TI-3086846864: base: teSt?
?TI-3076357008: base: teSt?
實際上我們期待的值應該 :
?TI-3086846864: base: test?
?TI-3076357008: base: teSt?
雖然只是一個字母的差別,但這其實涉及到函數 basename()的線程安全特征。造成這個問題的原因是函數 basename()的返回值指向了輸入字符串的一個片段,所以當輸入字符串發生變化以后,basename()的返回值也發生了變化。
因為 basename()的函數聲明只提供了一個參數,所以該函數不得不通過修改輸入參數或使用靜態變量的方式來將結果返回給用戶。
參考 Linux幫助手冊,dirname()和 basename()可能會修改傳入的參數字符串,所以調用者應該傳入參數字符串的拷貝。并且,這兩個函數的返回值可能指向靜態分配的內存,所以其返回值的內容有可能被隨后的 dirname()或 basename()調用修改。
因為多線程技術和線程安全概念出現得相對較晚,所以 POSIX規范中收納的一些函數并不符合線程安全要求。
下表是 UNIX環境高級編程列出 POSIX.1規范中的非線程安全的函數:
asctime ecvt gethostent getutxline putc_unlocked
basename encrypt getlogin gmtime putchar_unlocked
catgets endgrent getnetbyaddr hcreate putenv
crypt endpwent getnetbyname hdestroy pututxline
ctime endutxent getopt hsearch rand
dbm_clearerr fcvt getprotobyname inet_ntoa readdir
dbm_close ftw getprotobynumber L64a setenv
dbm_delete getcvt getprotobynumber lgamma setgrent
dbm_error getc_unlocked getprotoent lgammaf setkey
dbm_fetch getchar_unlocked getpwent lgammal setpwent
dbm_firstkey getdate getpwnam localeconv setutxent
dbm_nextkey getenv getpwuid lrand48 strerror
dbm_open getgrent getservbyname mrand48 strtok
dbm_store getgrgid getservbyport nftw ttyname
dirname getgrnam getservent nl_langinfo unsetenv
dlerror gethostbyaddr getutxent ptsname wcstombs
drand48 gethostbyname getutxid ptsname ectomb
目前大部分上述函數目前已經有了對應的線程安全版本的實現,例如:針對 getpwnam的 getpwnam_r(),( 這里的 _r表示可重入 (reentrant),如前所述,可重入的函數都是線程安全的)。在多線程軟件開發中,如果需要使用到上所述函數,應優先使用它們對應的線程安全版本。而對于某些沒有線程安全版本的函數,開發人員可按自己需要編寫線程安全版本的實現。
在編寫自己的線程安全版本函數之前,應首先仔細閱讀 POSIX標準對函數的定義,以及通過充分的測試熟悉函數的輸入和輸出。理論上來說,所有的線程安全的版本函數應該與非線程安全版本函數在單線程環境下表現一致。
這里給出一個針對 basename()的線程安全版本的例子。
?
在熟悉了 basename() 函數的功能之后,下面是一個線程安全版本的實現。
?/* thread-safe version of basename() */?
?char* basename_ta(char *path, char *buf, int buflen) {?
? ? #define DEFAULT_RESULT_DOT ? ? "."
? ? #define DEFAULT_RESULT_SLASH ? "/"
? ? /* 如果輸入的路徑長度小于 PATH_MAX,
?? ? * 則使用自動變量 i_fixed_bufer 作為內部緩沖區 ,?
?? ? * 否則申請堆內存做為字符串存放緩沖區。
?? ? */?
? ? char i_fixed_buf[PATH_MAX+1];?
? ? const int i_fixed_buf_len = sizeof(i_fixed_buf)/sizeof(char);?
? ? char *result = buf;?
? ? char *i_buf = NULL;?
? ? int i_buf_len = 0;?
? ? int adjusted_path_len = 0;?
? ? int path_len = 0;?
? ? int i, j;?
? ? char tmp = 0;?
? ? if (path == NULL) {?
? ? ? ? /* 如果輸入為空指針,則直接返回當前目錄 */?
? ? ? ? path = DEFAULT_RESULT_DOT;?
? ? }?
? ? /* 分配內部緩沖區用來存放輸入字符串 */?
? ? path_len = strlen(path);?
? ? if ((path_len + 1) > i_fixed_buf_len) {?
? ? ? ? i_buf_len = (path_len + 1);?
? ? ? ? i_buf = (char*) malloc(i_buf_len * sizeof(char));?
? ? } else {?
? ? ? ? i_buf_len = i_fixed_buf_len;?
? ? ? ? i_buf = i_fixed_buf;?
? ? }?
? ? /* 拷貝字符串到緩沖區,以便接下來對字符串做預處理 */?
? ? strcpy(i_buf, path);?
? ? adjusted_path_len = path_len;?
? ? /* 預處理:刪除路徑未的路徑符號 '/'; */?
? ? if (adjusted_path_len > 1) {?
? ? ? ? while (i_buf[adjusted_path_len-1] == '/') {?
? ? ? ? ? ? if (adjusted_path_len != 1) {?
? ? ? ? ? ? ? ? adjusted_path_len--;?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? break;?
? ? ? ? ? ? }?
? ? ? ? }?
? ? ? ? i_buf[adjusted_path_len] = '/0';?
? ? }?
? ? /* 預處理:折疊最后出現的連續 '/'; */?
? ? if (adjusted_path_len > 1) {?
? ? ? ? for (i = (adjusted_path_len -1), j = 0; i >= 0; i--) {?
? ? ? ? ? ? if (j == 0) {?
? ? ? ? ? ? ? ? if (i_buf[i] == '/')?
? ? ? ? ? ? ? ? ? ? j = i;?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? if (i_buf[i] != '/') {?
? ? ? ? ? ? ? ? ? ? i++;?
? ? ? ? ? ? ? ? ? ? break;?
? ? ? ? ? ? ? ? }?
? ? ? ? ? ? }?
? ? ? ? }?
? ? ? ? if (j != 0 && i < j) {?
? ? ? ? ? ? /* 折疊多余的路徑符號 '/';?
?? ? ? ? ? ? */?
? ? ? ? ? ? strcpy(i_buf+i, i_buf+j);?
? ? ? ? }?
? ? ? ? adjusted_path_len -= (j - i);?
? ? }?
? ? /* 預處理:尋找最后一個路徑符號 '/' */?
? ? for (i = 0, j = -1; i < adjusted_path_len; i++) {?
? ? ? ? if (i_buf[i] == '/')?
? ? ? ? ? ? j = i;?
? ? }?
? ? /* 查找 basename */?
? ? if (j >= 0) {?
? ? ? ? /* found one '/' */?
? ? ? ? if (adjusted_path_len == 1) {? /* 輸入的是跟路徑 ("/"),則返回根路徑 */?
? ? ? ? ? ? if (2 > buflen) {?
? ? ? ? ? ? ? ? return NULL;?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? strcpy(result, DEFAULT_RESULT_SLASH);?
? ? ? ? ? ? }?
? ? ? ? } else {?
? ? ? ? ? ? if ((adjusted_path_len - j) > buflen) {? /* 緩沖區不夠,返回空指針 */?
? ? ? ? ? ? ? ? result = NULL;?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? strcpy(result, (i_buf+j+1));?
? ? ? ? ? ? }?
? ? ? ? }?
? ? } else {?
? ? ? ? /* no '/' found? */?
? ? ? ? if (adjusted_path_len == 0) {?
? ? ? ? ? ? if (2 > buflen) {? ? ? ? ? ? ? /* 如果傳入的參數為空字符串 ("") */?
? ? ? ? ? ? ? ? return NULL;? ? ? ? ? ? ? ? /* 直接返回當前目錄 (".")? */?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? strcpy(result, DEFAULT_RESULT_DOT);?
? ? ? ? ? ? }?
? ? ? ? } else {?
? ? ? ? ? ? if ((adjusted_path_len+1) > buflen) { ? ? ?
? ? ? ? ? ? ? ? result = NULL; ? ? ? ? ? ? /* 緩沖區不夠,返回空指針? ? */?
? ? ? ? ? ? } else {?
? ? ? ? ? ? ? ? strcpy(result, i_buf); ? /* 拷貝整個字符串做為返回值 ? */?
? ? ? ? ? ? }?
? ? ? ? }?
? ? }?
? ? if (i_buf_len != i_fixed_buf_len) { ? /* 釋放緩沖區 ? ? ? ? */?
? ? ? ? free(i_buf);?
? ? ? ? i_buf = NULL;?
? ? }?
? ? return result;?
?}?
這個線程安全版本的函數將處理結果存儲在外部分配的內存中,所以函數內部并無對全局資源的再依賴。因此,這個函數可安全地被多個線程所使用。
Linux可重入函數與不可重入函數
2011-01-24 20:27 中國IT實驗室 佚名?
關鍵字:Linux
主要用于多任務環境中,一個可重入的函數簡單來說就是可以被中斷的函數,也就是說,可以在這個函數執行的任何時刻中斷它,轉入OS調度下去執行另外一段代碼,而返回控制時不會出現什么錯誤;而不可重入的函數由于使用了一些系統資源,比如全局變量區,中斷向量表等,所以它如果被中斷的話,可能會出現問題,這類函數是不能運行在多任務環境下的。
也可以這樣理解,重入即表示重復進入,首先它意味著這個函數可以被中斷,其次意味著它除了使用自己棧上的變量以外不依賴于任何環境(包括static),這樣的函數就是purecode(純代碼)可重入,可以允許有該函數的多個副本在運行,由于它們使用的是分離的棧,所以不會互相干擾。如果確實需要訪問全局變量(包括static),一定要注意實施互斥手段。可重入函數在并行運行環境中非常重要,但是一般要為訪問全局變量付出一些性能代價。
編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(即P、V操作)等手段對其加以保護。
說明:若對所使用的全局變量不加以保護,則此函數就不具有可重入性,即當多個進程調用此函數時,很有可能使有關全局變量變為不可知狀態。
示例:假設Exam是int型全局變量,函數Squre_Exam返回Exam平方值。那么如下函數不具有可重入性。
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
此函數若被多個進程調用的話,其結果可能是未知的,因為當(**)語句剛執行完后,另外一個使用本函數的進程可能正好被激活,那么當新激活的進程執行到此函數時,將使Exam賦與另一個不同的para值,所以當控制重新回到“temp = Square_Exam( )”后,計算出的temp很可能不是預想中的結果。此函數應如下改進。
unsigned int example( int para ) {
unsigned int temp;
[申請信號量操作] //(1)
Exam = para;
temp = Square_Exam( );
[釋放信號量操作]
return temp;
}
(1)若申請不到“信號量”,說明另外的進程正處于給Exam賦值并計算其平方過程中(即正在使用此信號),本進程必須等待其釋放信號后,才可繼續執行。若申請到信號,則可繼續執行,但其它進程必須等待本進程釋放信號量后,才能再使用本信號。
保證函數的可重入性的方法:
在寫函數時候盡量使用局部變量(例如寄存器、堆棧中的變量),對于要使用的全局變量要加以保護(如采取關中斷、信號量等方法),這樣構成的函數就一定是一個可重入的函數。
VxWorks中采取的可重入的技術有:
* 動態堆棧變量(各子函數有自己獨立的堆棧空間)
* 受保護的全局變量和靜態變量
* 任務變量
--------------------------------------------------
在實時系統的設計中,經常會出現多個任務調用同一個函數的情況。如果這個函數不幸被設計成為不可重入的函數的話,那么不同任務調用這個函數時可能修改其他任務調用這個函數的數據,從而導致不可預料的后果。那么什么是可重入函數呢?所謂可重入函數是指一個可以被多個任務調用的過程,任務在調用時不必擔心數據是否會出錯。不可重入函數在實時系統設計中被視為不安全函數。滿足下列條件的函數多數是不可重入的:
1) 函數體內使用了靜態的數據結構;
2) 函數體內調用了malloc()或者free()函數;
3) 函數體內調用了標準I/O函數。
下面舉例加以說明。
A. 可重入函數
void strcpy(char *lpszDest, char *lpszSrc)
{
while(*lpszDest++=*lpszSrc++);
*dest=0;
}
B. 不可重入函數1
charcTemp;//全局變量
void SwapChar1(char *lpcX, char *lpcY)
{
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//訪問了全局變量
}
C. 不可重入函數2
void SwapChar2(char *lpcX,char *lpcY)
{
static char cTemp;//靜態局部變量
cTemp=*lpcX;
*lpcX=*lpcY;
lpcY=cTemp;//使用了靜態局部變量
}
問題1,如何編寫可重入的函數?
答:在函數體內不訪問那些全局變量,不使用靜態局部變量,堅持只使用局部變量,寫出的函數就將是可重入的。如果必須訪問全局變量,記住利用互斥信號量來保護全局變量。
問題2,如何將一個不可重入的函數改寫成可重入的函數?
答:把一個不可重入函數變成可重入的唯一方法是用可重入規則來重寫它。其實很簡單,只要遵守了幾條很容易理解的規則,那么寫出來的函數就是可重入的。
1) 不要使用全局變量。因為別的代碼很可能覆蓋這些變量值。
2) 在和硬件發生交互的時候,切記執行類似disinterrupt()之類的操作,就是關閉硬件中斷。完成交互記得打開中斷,在有些系列上,這叫做“進入/退出核心”。
3) 不能調用其它任何不可重入的函數。
4) 謹慎使用堆棧。最好先在使用前先OS_ENTER_KERNAL。
堆棧操作涉及內存分配,稍不留神就會造成益出導致覆蓋其他任務的數據,所以,請謹慎使用堆棧!最好別用!很多黑客程序就利用了這一點以便系統執行非法代碼從而輕松獲得系統控制權。還有一些規則,總之,時刻記住一句話:保證中斷是安全的!
實例問題:曾經設計過如下一個函數,在代碼檢視的時候被提醒有bug,因為這個函數是不可重入的,為什么?
unsigned int sum_int( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是static類型
for (index = 1; index <= base; index++)
sum += index;
return sum;
}
分析:所謂的函數是可重入的(也可以說是可預測的),即只要輸入數據相同就應產生相同的輸出。這個函數之所以是不可預測的,就是因為函數中使用了static變量,因為static變量的特征,這樣的函數被稱為:帶“內部存儲器”功能的的函數。因此如果需要一個可重入的函數,一定要避免函數中使用static變量,這種函數中的static變量,使用原則是,能不用盡量不用。
將上面的函數修改為可重入的函數,只要將聲明sum變量中的static關鍵字去掉,變量sum即變為一個auto類型的變量,函數即變為一個可重入的函數。
當然,有些時候,在函數中是必須要使用static變量的,比如當某函數的返回值為指針類型時,則必須是static的局部變量的地址作為返回值,若為auto類型,則返回為錯指針。