關於static和可重入函數

1、概述

  static 聲明的變量在C語言中有兩方面的特徵:

  1)、變量會被放在程序的全局存儲區中,這樣可以在下一次調用的時候還可以保持原來的賦值。這一點是它與堆棧變量和堆變量的區別。

 
 
  2)、變量用static告知編譯器,自己僅僅在變量的作用範圍內可見。這一點是它與全局變量的區別。

  2、問題:Static的理解

  關於static變量,請選擇下面所有說法正確的內容:

  A、若全局變量僅在單個C文件中訪問,則可以將這個變量修改爲靜態全局變量,以降低模塊間的耦合度;

  B、若全局變量僅由單個函數訪問,則可以將這個變量改爲該函數的靜態局部變量,以降低模塊間的耦合度;

  C、設計和使用訪問動態全局變量、靜態全局變量、靜態局部變量的函數時,需要考慮重入問題;

  D、靜態全局變量過大,可那會導致堆棧溢出。

  答案與分析:

  對於A,B:根據本篇概述部分的說明b),我們知道,A,B都是正確的。

  對於C:根據本篇概述部分的說明a),我們知道,C是正確的(所謂的函數重入問題,下面會詳細闡述)。

  對於D:靜態變量放在程序的全局數據區,而不是在堆棧中分配,所以不可能導致堆棧溢出,D是錯誤的。

  因此,答案是A、B、C。

  3、問題:不可重入函數

  曾經設計過如下一個函數,在代碼檢視的時候被提醒有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類型,則返回爲錯指針。

 

 

C++ 類的靜態成員(static)
 

 

  靜態成員的提出是爲了解決數據共享的問題。實現共享有許多方法,如:設置全局性的變量或對象是一種方法。但是,全局變量或對象是有侷限性的。這一章裏,我們主要講述類的靜態成員來實現數據的共享。

  靜態數據成員

  在類中,靜態成員可以實現多個對象之間的數據共享,並且使用靜態數據成員還不會破壞隱藏的原則,即保證了安全性。因此,靜態成員是類的所有對象中共享的成員,而不是某個對象的成員。

  使用靜態數據成員可以節省內存,因爲它是所有對象所公有的,因此,對多個對象來說,靜態數據成員只存儲一處,供所有對象共用。靜態數據成員的值對每個對象都是一樣,但它的值是可以更新的。只要對靜態數據成員的值更新一次,保證所有對象存取更新後的相同的值,這樣可以提高時間效率。

  靜態數據成員的使用方法和注意事項如下:

  1、靜態數據成員在定義或說明時前面加關鍵字static。

  2、靜態成員初始化與一般數據成員初始化不同。靜態數據成員初始化的格式如下:

    <數據類型><類名>::<靜態數據成員名>=<值>

  這表明:

        (1) 初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆。

  (2) 初始化時不加該成員的訪問權限控制符private,public等。

  (3) 初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員。

  3、靜態數據成員是靜態存儲的,它是靜態生存期,必須對它進行初始化。

  4、引用靜態數據成員時,採用如下格式:

   <類名>::<靜態成員名>

  如果靜態數據成員的訪問權限允許的話(即public的成員),可在程序中,按上述格式來引用靜態數據成員。

  下面舉一例子,說明靜態數據成員的應用:

#include

class Myclass

{

public:

Myclass(int a, int b, int c);

void GetNumber();

void GetSum();

private:

int A, B, C;

static int Sum;

};

int Myclass::Sum = 0;

Myclass::Myclass(int a, int b, int c)

{

A = a;

B = b;

C = c;

Sum += A+B+C;

}

void Myclass::GetNumber()

{

cout<<"Number="<<<","<<<","<<< P>

}

void Myclass::GetSum()

{

cout<<"Sum="<<< P>

}

void main()

{

Myclass M(3, 7, 10),N(14, 9, 11);

M.GetNumber();

N.GetNumber();

M.GetSum();

N.GetSum();

}

  從輸出結果可以看到Sum的值對M對象和對N對象都是相等的。這是因爲在初始化M對象時,將M對象的三個int型數據成員的值求和後賦給了Sum,於是Sum保存了該值。在初始化N對象時,對將N對象的三個int型數據成員的值求和後又加到Sum已有的值上,於是Sum將保存另後的值。所以,不論是通過對象M還是通過對象N來引用的值都是一樣的,即爲54。

 

靜態成員函數

  靜態成員函數和靜態數據成員一樣,它們都屬於類的靜態成員,它們都不是對象成員。因此,對靜態成員的引用不需要用對象名。

  在靜態成員函數的實現中不能直接引用類中說明的非靜態成員,可以引用類中說明的靜態成員。如果靜態成員函數中要引用非靜態成員時,可通過對象來引用。下面通過例子來說明這一點。

#include
class M
{
public:
M(int a) { A=a; B+=a;}
static void f1(M m);
private:
int A;
static int B;
};

void M::f1(M m)
{
cout<<"A="<<
cout<<"B="<<
}

int M::B=0;
void main()
{
M P(5),Q(10);
M::f1(P); file://調用時不用對象名
M::f1(Q);
}

  讀者可以自行分析其結果。從中可看出,調用靜態成員函數使用如下格式:

   <類名>::<靜態成員函數名>(<參數表>);

 

在多線程條件下,函數應當是線程安全的,進一步,更強的條件是可重入的。

一個可重入函數保證了在多線程條件下,函數的狀態不會出現錯誤。

eg. in c

static int tmp;

void swap1(int* x, int* y) {

    tmp=*x;

    *x=*y;

    *y=tmp;

}

void swap2(int* x, int* y) {

    int tmp;

    tmp=*x;

    *x=*y;

    *y=tmp;

}

swap1是不可重入的,swap是可重入的。因爲在多線程條件下,操作系統會在swap1還沒有執行完的情況下,切換到另一個線程中,那個線程可能再次調用swap1,這樣狀態就錯了。

一個函數如果滿足以及下條件之一,那麼它是不可重入的:

  • 函數中使用了靜態變量,無論是全局靜態變量還是局部靜態變量。
  • 函數返回靜態變量。
  • 函數中調用了不可重入函數。
  • 函數是singleton中的成員函數而且使用了不使用線程獨立存儲的成員變量

總的來說,如果一個函數在重入條件下使用了未受保護的共享的資源,那麼它是不可重入的

什麼是可重入性?

可重入(reentrant)函數可以由多於一個任務併發使用,而不必擔心數據錯誤。相反, 不可重入(non-reentrant)函數不能由超過一個任務所共享,除非能確保函數的互斥(或者使用信號量,或者在代碼的關鍵部分禁用中斷)。可重入函數可以在任意時刻被中斷,稍後再繼續運行,不會丟失數據。可重入函數要麼使用本地變量,要麼在使用全局變量時保護自己的數據。

可重入函數:

  • 不爲連續的調用持有靜態數據。
  • 不返回指向靜態數據的指針;所有數據都由函數的調用者提供。
  • 使用本地數據,或者通過製作全局數據的本地拷貝來保護全局數據。
  • 絕不調用任何不可重入函數。
發佈了32 篇原創文章 · 獲贊 8 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章