1. 程式人生 > >static與volatile的用法

static與volatile的用法

訪問 ima 簡單 靜態局部變量 ons 特殊地址 返回 ase 硬件

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類型,則返回為錯指針。


volatile

volatile關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。

volatile譯為:易變的。在前面加上關鍵字volatile之後告訴編譯器,用volatile修飾的變量是易變的,不要對此變量進行優化,每次都要到變量的地址中去讀取變量的數據,但正因為這樣,才是保持了變量的原樣,因為變量已經發生改變了,你卻操作的是沒有變化時的數據,這樣才讓變量失去了本應該保持的屬性。

eg:

int a=1;

a=2;

a=3;

....

編譯器看到這樣的代碼,會覺得a的值只有a=3才有意義,所以把a存儲在一個寄存器中,每次遇到a都在這個寄存器中去讀取數據,但是a是可能改變,比如中斷或者多線程的時候。這個有可能你測試它又是正確的,因為隨著你的優化等級提高,生成的匯編代碼會有很大不同,如果基礎不夠紮實,代碼的魯棒性就會減弱,要想不這樣,那麽需要程序員有足夠紮實的基本功。

1.我們先看volatile第一個應用場景,在中斷服務函數中的使用。

/* main.c */

int flag=0;

int main(void)

{

  if(flag==1)

    {do somethings}

  if(flag==2)

   {do somethings}

  return 0;

}

/* interrupt*/

void NVIC_Handler(void)

{

  flag=1;

}

在這種情況下,編譯器可能會對其做優化,雖然中斷服務函數改變了flag的值,但是編譯器並沒有在變量內存中去讀取,而是在寄存器中讀取了flag之前的緩存數據。在中斷函數中的交互變量,一定要加上volatile關鍵字修飾,這樣每次讀取flag的值都是在其內存地址中讀取的,確保是我們想要的數據。

2.多任務環境下各任務間共享的標誌應該加volatile。原因其實和上面中斷一樣,要共享標誌,又不想讓編譯器優化了這一點,需要加上該修飾詞。

3.存儲器映射的硬件寄存器通常也要加voliate,因為每次對它的讀寫都可能有不同意義。

以STM32為例,寄存器中的數據也是時刻在變化的,我們也不想編譯器優化這一點,所以在庫函數中我們可以看到這樣的代碼。

技術分享圖片

這是寄存器的結構體,我們查看前綴__I 和__IO到底是什麽?

技術分享圖片

可以看到,在寄存器的映射中,也需要volatile,因為寄存器的值也是可能隨時更改的。

通過上面,我們也應該明白那個問題。

一個參數既可以是const還可以是volatile嗎?

可以的,例如只讀的狀態寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。軟件不能改變,並不意味著我硬件不能改變你的值,這就是單片機中的應用。

技術分享圖片

static與volatile的用法