1. 程式人生 > >C語言中的一些關鍵字

C語言中的一些關鍵字

volatile關鍵字

volatile關鍵字以前用的很少,但是在進行nRF51822定時器程式設計時,碰到在如下程式段,結合程式分析volatile關鍵字的作用。
#include "nrf51.h"
#include "nrf_gpio.h"
#include "led.h"
#include "time.h"
#include <stdbool.h>
#include <stdint.h>



/**
 * @brief Function for timer initialization.
 */
static volatile NRF_TIMER_Type * timer_init(timer_t timer)
{
    volatile NRF_TIMER_Type * p_timer;

    // 開始16 MHz晶振.
    NRF_CLOCK->EVENTS_HFCLKSTARTED  = 0;
    NRF_CLOCK->TASKS_HFCLKSTART     = 1;

    // 等待外部振盪器啟動
    while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) 
    {
        // Do nothing.
    }

    switch (timer)
    {
        case TIMER0:
            p_timer = NRF_TIMER0;
            break;

        case TIMER1:
            p_timer = NRF_TIMER1;
            break;

        case TIMER2:
            p_timer = NRF_TIMER2;
            break;

        default:
            p_timer = 0;
            break;
    }
    return p_timer;
}


/** @brief Function for using the peripheral hardware timers to generate an event after requested number of milliseconds.
 *
 * @param[in] timer Timer to be used for delay, values from @ref p_timer
 * @param[in] number_of_ms Number of milliseconds the timer will count.
 * @note This function will power ON the requested timer, wait until the delay, and then power OFF that timer.
 */

void nrf_timer_delay_ms(timer_t timer, uint_fast16_t volatile number_of_ms)
{
    volatile NRF_TIMER_Type * p_timer = timer_init(timer);

    if (p_timer == 0) 
    {
        while(true) 
        {
            // Do nothing.
        }
    }

    p_timer->MODE           = TIMER_MODE_MODE_Timer;        // 設定為定時器模式
    p_timer->PRESCALER      = 9;                            // Prescaler 9 produces 31250 Hz timer frequency => 1 tick = 32 us.
    p_timer->BITMODE        = TIMER_BITMODE_BITMODE_16Bit;  // 16 bit 模式.
    p_timer->TASKS_CLEAR    = 1;                            // 清定時器.
    
    // With 32 us ticks, we need to multiply by 31.25 to get milliseconds.
    p_timer->CC[0]          = number_of_ms * 31;
    p_timer->CC[0]         += number_of_ms / 4; 
    p_timer->TASKS_START    = 1;                    // Start timer.

    while (p_timer->EVENTS_COMPARE[0] == 0)
    {
        // Do nothing.
    }

    p_timer->EVENTS_COMPARE[0]  = 0;
    p_timer->TASKS_STOP         = 1;                // Stop timer.
}
/** @} */
在分析程式之前,先來介紹一下volatile關鍵字。在這裡是根據《C語言深度解剖》一書的理解來寫的。 volatile本身的意思就是易變的、不穩定的意思。而C語言在編譯的時候往往會對程式進行優化,但是對一些特殊的地址進行優化可能會得到適得其反的效果(在這裡僅僅是自己得到的理論,還沒有驗證與碰到過)。因此,在遇到這個關鍵字宣告的變數,編譯器對訪問該變數的程式碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。下面是關於volatile關鍵字對比的兩個例子。
int i=10;
int j=i;		//1語句
int k=i;		//2語句
此時編譯器對程式碼進行優化,由於1語句和2語句之間變數i並沒有被賦值,也就是i的值沒有發生改變,所以編譯器在1語句時從記憶體中取出i的值賦給j之後,這個值並沒有被丟掉,而是在2語句時繼續用這個值給k賦值。編譯器不會生成出彙編程式碼重新從記憶體裡取i的值,這樣提高了效率。 另一個例子:
volatile int i=10;
int j=i;		//3語句
int k=i;		//4語句
此時,volatile告訴編譯器,i是隨時可能發生變化的,每次使用它的時候必須從記憶體中取出i的值,因而編譯器生成的彙編程式碼會重新從i的地址處讀取資料放在k中。 如果i是一個暫存器變數,表示一個埠資料或者是多個執行緒共享資料,那麼就容易出錯,所以說volatile可以保證對特殊地址的穩定訪問。 總結: volatile提醒編譯器它後面所定義的變數隨時都有可能改變,因此編譯後的程式每次需要儲存或讀取這個變數的時候,都會直接從變數地址中讀取資料。如果沒有volatile關鍵字,則編譯器可能優化讀取和儲存,可能暫時使用暫存器中的值,如果這個變數由別的程式更新了的話,將出現不一致的現象。 簡單來說,就是自己所定義的變數在程式執行過程中一直會變,如果希望這個值被正確處理,就需要每次從記憶體中去讀這個值,這樣就不會有錯誤了,volatile關鍵字就是這個作用。 一般來說,volatile關鍵字用在如下的幾個地方: 1、中斷服務程式中修改的供其他程式檢測的變數需要加volatile; 2、多工環境下各任務間共享的標誌應該加volatile; 3、儲存器對映的硬體暫存器通常也要加volatile說明,因此每次對它的讀寫都可能有不同意義。 瞭解了這麼多關於volatile關鍵字的知識以及用法,現在來分析nRF51822定時器程式。 在nRF51822中,用volatile這個型別修飾符來修飾NRF_TIMER_TYPE *p_timer這個指向函式的指標變數,也就是說明這個指標變數是易變的,編譯器在訪問該變數的時候不再進行優化,而是直接從變數地址中讀取資料。 在此,對程式按照執行流程進行分析。當主函式這樣呼叫函式nrf_timer_delay_ms(TIMER0,TIMER_DELAY_MS)(TIMER_DELAY_MS為在標頭檔案中的巨集定義,為1000UL)時,程式進入子函式中。在此個人感覺子函式形參變數number_of_ms用volatile修飾符修飾沒用,因為TIMER_DELAY_MS本來就為一巨集定義變數
,並不會在中斷程式中或者上邊說的其他情況下進行改變。而p_timer這個指標變數用volatile修飾符來修飾,可能是為了避免其他可能產生的情況使其發生變化,但是總的來說感覺也非必須。有待刪掉之後進行驗證。 

static關鍵字

首先說一下static關鍵字的三個明顯的作用: 1、在函式體中,一個被宣告為靜態的變數在這一函式被呼叫過程中維持其值不變(該變數存放在靜態變數去) 2、在模組內(但是在函式外),一個被宣告為靜態的變數可以被模組內所用的函式訪問,但是不能被模組外其他函式訪問,它是一個本地的全域性變數。 3、在模組內,一個被宣告為靜態的函式只可被這一模組內的其他函式使用。那就是,這個函式被限制在宣告它的模組的本地範圍內使用。

extern關鍵字