Linux鎖機制和線程安全

鎖機制是多線程編程中最常用的同步機制,用來對多線程間共享的臨界區進行保護。

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),一定要注意實施互斥手段。可重入函數在并行運行環境中非常重要,但是一般要為訪問全局變量付出一些性能代價。

  編寫可重入函數時,若使用全局變量,則應通過關中斷、信號量(PV操作)等手段對其加以保護。

  說明:若對所使用的全局變量不加以保護,則此函數就不具有可重入性,即當多個進程調用此函數時,很有可能使有關全局變量變為不可知狀態。

  示例:假設Examint型全局變量,函數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類型,則返回為錯指針。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383307.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383307.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383307.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c++中的繼承--3(多繼承問題,菱形繼承)

繼承中的多繼承 #include<iostream>using namespace std;class Base1 { public:Base1(){m_A 10;} public:int m_A;};class Base2 { public:Base2(){m_A 10;} public:int m_B;int m_A;};class Son :public Base1, public Base2 {public:int m_C;int m_D; };void test01…

c++中的多態---1(多態概念,靜態聯編和動態聯編,多態原理解析,重載,重寫,重定義的對比)

多態的基本概念 多態是面向對象設計語言數據抽象和繼承之外的第三個基本特征多態性(polymorphism)提供接口與具體實現之間的另一層隔膜&#xff0c;從而將“what”和“how”分離開來&#xff0c;多態性改善了代碼的可讀和組織性&#xff0c;同時也使創建的程序具有可擴展性&am…

Ubuntu下各種服務搭建及操作技巧

Ubuntu下搭建TFTP 1、安裝軟件包 sudo apt-get install tftpd tftp xinetd 2、建立配置文件 在/etc/xinetd.d/下建立一個配置文件tftp sudo vi /etc/xinetd.d/tftp 內容如下 service tftp { socket_type dgram protocol udp wait yes user root …

c++多態--2(計算器,純虛函數和抽象類)

為什么要用多態 早期方法不利于擴展開閉原則 開閉原則 對擴展開放 對修改關閉利用多態實現—利于后期擴展&#xff0c;結構性非常好&#xff0c;可讀性高&#xff0c;效率稍微低&#xff0c;發生多態內部結構復雜 多態成立的條件 又繼承 子類重寫父類虛函數的函數&#xff1…

使用Automake和Autoconf生成Makefile

automake 所產生的 Makefile 除了可以做到程序的自動編譯和鏈接 外&#xff0c;還可以用來生成各種文檔&#xff08;如manual page、info文件&#xff09;&#xff0c;可以將源代碼文件包裝起來以供發布。所以程序源代碼所存放的目錄 結構最好符合GNU的標準慣例。下面以hello.…

c++中多態---3(虛析構和純虛析構,向上類型轉化和向下類型轉化)

虛析構和純虛析構 虛析構virtual ~類名(){}類內聲明&#xff0c;類內實現解決問題&#xff1a;通過父類指針指向子類對象釋放時候不干凈的問題 純虛析構 寫法 virtual ~類名(){}0; 類內聲明 類外實現 如果出現了純虛析構函數&#xff0c;這個類也算是抽象類&#xff0c;不可…

嵌入式開發硬件知識札記

三態邏輯 1. 概念 三態指其輸出既可以是一般二值邏輯電路&#xff0c;即正常的高電平&#xff08;邏輯1&#xff09;或低電平&#xff08;邏輯0&#xff09;&#xff0c;又可以保持特有的高阻抗狀態。高阻態相當于隔斷狀態&#xff08;電阻很大&#xff0c;相當于開路&#xff…

《凡人修仙傳》中打斗場景(c++多態實現)

我們 要實現打斗場景&#xff0c;第一&#xff0c;我們需要有打斗的雙方&#xff0c;一個是英雄&#xff0c;一個是怪物&#xff0c;他們都有自己的屬性&#xff0c;比如攻擊&#xff0c;防御&#xff0c;血量。其次我們的英雄還會有武器。武器上有一些加成屬性&#xff0c;可以…

使用mp4v2將aac音頻h264視頻數據封裝成mp4開發心得

這陣子在搗鼓一個將游戲視頻打包成本地可播放文件的模塊。開始使用avi作為容器&#xff0c;弄了半天無奈avi對aac的支持實在有限&#xff0c;在播放時音視頻時無法完美同步。 關于這點avi文檔中有提到&#xff1a; For AAC, one RAW AAC frame usually spans over 1024 samples…

c++模板---1(模板概念,利用模板實現數組排序,函數模板調用規則)

什么叫泛型編程&#xff1f;1. 參數類型化。 2. 模板 模板概念 c提供了函數模板&#xff0c;所謂函數模板&#xff0c;實際上是建立一個通用函數&#xff0c;其函數類型和形參類型不具體制定&#xff0c;用一個虛擬的類型來代表。這個通用函數就成為函數模板。凡是函數體相同…

c++模板--2(模板機制,模板的局限性,類模板,類模板做函數的參數)

函數模板機制結論 編譯器并不是把函數模板處理成能狗處理任何類型的函數函數模板通過具體類型產生不同的函數編譯器會對函數模板進行兩次編譯&#xff0c;在聲明的地方對模板代碼的本身進行編譯&#xff0c;在調用的地方對參數替換后代碼進行編譯在編譯器編譯階段&#xff0c;…

arm-linux 交叉編譯 mp4v2

2014-09-23 14:44 1901人閱讀 評論(0) 收藏 舉報 版權聲明&#xff1a;本文為博主原創文章&#xff0c;未經博主允許不得轉載。 1、下載mp4v2的源代碼&#xff08;http://code.google.com/p/mp4v2/downloads/list&#xff09;mp4v2-2.0.0.tar.bz2 2. 進入相應目錄 ./configu…

c++模板---3(類模板碰到繼承問題,類模板類外實現,類模板與友元函數)

類模板碰到繼承問題 基類如果是模板類&#xff0c;必須讓子類告訴編譯器 基類中的T到底是什么類型 如果不告訴&#xff0c;那么無法分配內存&#xff0c;編譯不過 利用參數列表class Child :public Base<int> #include<iostream>using namespace std;template&l…

Linux USB札記

嵌入式linux內核添加USB模塊&#xff08;U盤&#xff09;支持 使用menuconfig工具進行配置 1、Device Drivers->SCSI device support->SCSI disk support此選項必須勾選 2、Device Drivers->USB support->Support for Host-side USB 此選項選中后會出現子菜單&…

c++實現任意類型數組類的封裝

MyArray.hpp #pragma once #include<iostream> #include<string> using namespace std; template<class T>class MyArray { public://構造explicit MyArray(int capacity) //防止隱式類型轉換&#xff0c;防止MyArray arr 10{this->m_Capacity capacity…

Linux內核配置選項 (經典學習)

轉載地址http://book.csdn.net/bookfiles/972/10097230254.shtml 2.5 Linux內核配置選項 下面以最新的Linux 2.6.20內核為例&#xff0c;介紹比較常用的一些Linux內核配置選項&#xff0c;其他選項讀者可以參考系統提供的幫助信息。 需要說明的是&#xff0c;在內核配置中&am…

深入理解linux系統下proc文件系統內容

另外&#xff0c;可以參考這個http://www.centos.org/docs/5/html/5.1/Deployment_Guide/ch-proc.html 內容摘要&#xff1a;Linux系統上的/proc目錄是一種文件系統&#xff0c;即proc文件系統。 Linux系統上的/proc目錄是一種文件系統&#xff0c;即proc文件系統。與其它常見…

c++中的異常--1(基本概念, c語言中處理異常,c++中處理異常,異常的基本使用,棧解旋)

異常基本概念 異常處理就是處理程序中的錯誤&#xff0c;所謂錯誤是指在程序運行的過程中發生的一些異常事件&#xff08;如&#xff1a;除0退出&#xff0c;數組下標越界&#xff0c;所要讀取的文件不存在&#xff0c;空指針&#xff0c;內存不足等等&#xff09; c語言中處…

RGB、YUV和YCbCr

之前對RGB、YUV和YCbCr一直沒有清晰的理解和認識&#xff0c;今天打算做一個小結&#xff0c;結合網上的文章談談自己的看法&#xff0c;也希望有機會看到這篇文章的人能指點一二&#xff0c;相互交流&#xff0c;共同進步。 首先要說明&#xff0c;上述的RGB、YUV和YCbCr都是人…

c++中的異常---2(異常接口聲明,異常變量的生命周期,異常的多態使用)

異常接口聲明 為了加強程序的可讀性&#xff0c;可以在函數聲明中列出可能拋出異常的所有類型&#xff0c;例如&#xff1a;void func() throw(A,B,C);這個函數func能夠且只能拋出類型A,B,C及其子類的異常如果在函數聲明中沒有包含異常接口聲明&#xff0c;則此函數可以拋任何…