溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

c語言volatile關鍵字有什么用

發(fā)布時間:2020-11-02 11:03:01 來源:億速云 閱讀:172 作者:小新 欄目:編程語言

這篇文章主要介紹了c語言volatile關鍵字有什么用,具有一定借鑒價值,需要的朋友可以參考下。希望大家閱讀完這篇文章后大有收獲。下面讓小編帶著大家一起了解一下。

c語言volatile關鍵字的作用:提醒編譯器它后面所定義的變量隨時都有可能改變,因此編譯后的程序每次需要存儲或讀取這個變量的時候,告訴編譯器對該變量不做優(yōu)化,都會直接從變量內(nèi)存地址中讀取數(shù)據(jù),從而可以提供對特殊地址的穩(wěn)定訪問,以免出錯。

教程

一.前言

1.編譯器優(yōu)化介紹:

由于內(nèi)存訪問速度遠不及CPU處理速度,為提高機器整體性能,在硬件上引入硬件高速緩存Cache,加速對內(nèi)存的訪問。另外在現(xiàn)代CPU中指令的執(zhí)行并不一定嚴格按照順序執(zhí)行,沒有相關性的指令可以亂序執(zhí)行,以充分利用CPU的指令流水線,提高執(zhí)行速度。以上是硬件級別的優(yōu)化。再看軟件一級的優(yōu)化:一種是在編寫代碼時由程序員優(yōu)化,另一種是由編譯器進行優(yōu)化。編譯器優(yōu)化常用的方法有:將內(nèi)存變量緩存到寄存器;調(diào)整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規(guī)內(nèi)存進行優(yōu)化的時候,這些優(yōu)化是透明的,而且效率很好。由編譯器優(yōu)化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執(zhí)行的操作之間設置內(nèi)存屏障(memory barrier),linux 提供了一個宏解決編譯器的執(zhí)行順序問題。

void Barrier(void)

這個函數(shù)通知編譯器插入一個內(nèi)存屏障,但對硬件無效,編譯后的代碼會把當前CPU寄存器中的所有修改過的數(shù)值存入內(nèi)存,需要這些數(shù)據(jù)的時候再重新從內(nèi)存中讀出。

2.volatile總是與優(yōu)化有關,編譯器有一種技術叫做數(shù)據(jù)流分析,分析程序中的變量在哪里賦值、在哪里使用、在哪里失效,分析結(jié)果可以用于常量合并,常量傳播等優(yōu)化,進一步可以消除一些代碼。但有時這些優(yōu)化不是程序所需要的,這時可以用volatile關鍵字禁止做這些優(yōu)化。

二.volatile詳解:

1.原理作用:

volatile的本意是“易變的” ,因為訪問寄存器要比訪問內(nèi)存單元快的多,所以編譯器一般都會作減少存取內(nèi)存的優(yōu)化,但有可能會讀臟數(shù)據(jù)。

當要求使用volatile聲明變量值的時候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過數(shù)據(jù)。

精確地說就是,遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優(yōu)化(都會直接從變量內(nèi)存地址中讀取數(shù)據(jù)),從而可以提供對特殊地址的穩(wěn)定訪問;如果不使用valatile,則編譯器將對所聲明的語句進行優(yōu)化。(簡潔的說就是:volatile關鍵詞影響編譯器編譯的結(jié)果,用volatile聲明的變量表示該變量隨時可能發(fā)生變化,與該變量有關的運算,不要進行編譯優(yōu)化,以免出錯)

2.看兩個事例:

1>告訴compiler不能做任何優(yōu)化

比如要往某一地址送兩指令:

int *ip =...; //設備地址 
*ip = 1; //第一個指令 
*ip = 2; //第二個指令

以上程序compiler可能做優(yōu)化而成:

int *ip = ...; 
*ip = 2;

結(jié)果第一個指令丟失。如果用volatile, compiler就不允許做任何的優(yōu)化,從而保證程序的原意:

volatile int *ip = ...; 
*ip = 1; 
*ip = 2;

即使你要compiler做優(yōu)化,它也不會把兩次付值語句間化為一。它只能做其它的優(yōu)化。

2>用volatile定義的變量會在程序外被改變,每次都必須從內(nèi)存中讀取,而不能重復使用放在cache或寄存器中的備份。

例如:

volatile char a;
a=0;
while(!a){
//do some things;
}
doother();

如果沒有 volatiledoother()不會被執(zhí)行

3.下面是使用volatile變量的幾個場景:

1>中斷服務程序中修改的供其它程序檢測的變量需要加volatile;

例如:

static int i=0;
int main(void)
{
     ...
     while (1){
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
      i=1;
}

程序的本意是希望ISR_2中斷產(chǎn)生時,在main函數(shù)中調(diào)用dosomething函數(shù),但是,由于編譯器判斷在main函數(shù)里面沒有修改過i,因此可能只執(zhí)行一次對從i到某寄存器的讀操作,然后每次if判斷都只使用這個寄存器里面的“i副本”,導致dosomething永遠也不會被調(diào)用。如果將變量加上volatile修飾,則編譯器保證對此變量的讀寫操作都不會被優(yōu)化(肯定執(zhí)行)。此例中i也應該如此說明。

2>多任務環(huán)境下各任務間共享的標志應該加volatile

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

例如:

假設要對一個設備進行初始化,此設備的某一個寄存器為0xff800000。

int  *output = (unsigned  int *)0xff800000;//定義一個IO端口;
int   init(void)
{
      int i;
      for(i=0;i< 10;i++){
         *output = i;
      }
}

經(jīng)過編譯器優(yōu)化后,編譯器認為前面循環(huán)半天都是廢話,對最后的結(jié)果毫無影響,因為最終只是將output這個指針賦值為9,所以編譯器最后給你編譯編譯的代碼結(jié)果相當于:

int  init(void)
{
      *output = 9;
}

如果你對此外部設備進行初始化的過程是必須是像上面代碼一樣順序的對其賦值,顯然優(yōu)化過程并不能達到目的。反之如果你不是對此端口反復寫操作,而是反復讀操作,其結(jié)果是一樣的,編譯器在優(yōu)化后,也許你的代碼對此地址的讀操作只做了一次。然而從代碼角度看是沒有任何問題的。這時候就該使用volatile通知編譯器這個變量是一個不穩(wěn)定的,在遇到此變量時候不要優(yōu)化。

例如:

volatile  int *output=(volatile unsigned int *)0xff800000;//定義一個I/O端口

另外,以上這幾種情況經(jīng)常還要同時考慮數(shù)據(jù)的完整性(相互關聯(lián)的幾個標志讀了一半被打斷了重寫),在1中可以通過關中斷來實現(xiàn),2中禁止任務調(diào)度,3中則只能依靠硬件的良好設計。

4.幾個問題

1)一個參數(shù)既可以是const還可以是volatile嗎?

可以的,例如只讀的狀態(tài)寄存器。它是volatile因為它可能被意想不到地改變。它是const因為程序不應該試圖去修改它。

2) 一個指針可以是volatile 嗎?

可以,當一個中服務子程序修該一個指向一個buffer的指針時。

5.volatile的本質(zhì):

1> 編譯器的優(yōu)化

在本次線程內(nèi), 當讀取一個變量時,為提高存取速度,編譯器優(yōu)化時有時會先把變量讀取到一個寄存器中;以后,再取變量值時,就直接從寄存器中取值;當變量值在本線程里改變時,會同時把變量的新值copy到該寄存器中,以便保持一致。

當變量在因別的線程等而改變了值,該寄存器的值不會相應改變,從而造成應用程序讀取的值和實際的變量值不一致。

當該寄存器在因別的線程等而改變了值,原變量的值不會改變,從而造成應用程序讀取的值和實際的變量值不一致。

2>volatile應該解釋為“直接存取原始內(nèi)存地址”比較合適,“易變的”這種解釋簡直有點誤導人。

6.下面的函數(shù)有什么錯誤:

int square(volatile int *ptr)
{
return *ptr * *ptr;
}

該程序的目的是用來返指針*ptr指向值的平方,但是,由于*ptr指向一個volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼:

int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結(jié)果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

注意:頻繁地使用volatile很可能會增加代碼尺寸和降低性能,因此要合理的使用volatile。

感謝你能夠認真閱讀完這篇文章,希望小編分享c語言volatile關鍵字有什么用內(nèi)容對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業(yè)資訊頻道,遇到問題就找億速云,詳細的解決方法等著你來學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI