注意:本文中代碼均使用 Qt 開發(fā)編譯環(huán)境
volatile關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改创夜。
用volatile關(guān)鍵字聲明的變量i每一次被訪問時(shí),執(zhí)行部件都會(huì)從i相應(yīng)的內(nèi)存單元中取出i的值熬甚。
沒有用volatile關(guān)鍵字聲明的變量i在被訪問的時(shí)候可能直接從cpu的寄存器中取值(因?yàn)橹癷被訪問過冤狡,也就是說之前就從內(nèi)存中取出i的值保存到某個(gè)寄存器中),之所以直接從寄存器中取值递雀,而不去內(nèi)存中取值柄延,是因?yàn)榫幾g器優(yōu)化代碼的結(jié)果(訪問cpu寄存器比訪問ram快的多)。
以上兩種情況的區(qū)別在于被編譯成匯編代碼之后缀程,兩者是不一樣的搜吧。之所以這樣做是因?yàn)樽兞縤可能會(huì)經(jīng)常變化,保證對(duì)特殊地址的穩(wěn)定訪問杨凑。
volatile關(guān)鍵字是一種類型修飾符滤奈,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統(tǒng)撩满、硬件或者其它線程等蜒程。遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對(duì)訪問該變量的代碼就不再進(jìn)行優(yōu)化鹦牛,從而可以提供對(duì)特殊地址的穩(wěn)定訪問搞糕。
使用該關(guān)鍵字的例子如下:
volatile int nVint;
當(dāng)要求使用volatile 聲明的變量的值的時(shí)候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù)曼追,即使它前面的指令剛剛從該處讀取過數(shù)據(jù)窍仰。而且讀取的數(shù)據(jù)立刻被保存。
例如:
volatile int i=10;
int a = i;
...
//其他代碼礼殊,并未明確告訴編譯器驹吮,對(duì)i進(jìn)行過操作
int b = i;
volatile 指出 i是隨時(shí)可能發(fā)生變化的针史,每次使用它的時(shí)候必須從i的地址中讀取,因而編譯器生成的匯編代碼會(huì)重新從i的地址讀取數(shù)據(jù)放在b中碟狞。而優(yōu)化做法是啄枕,由于編譯器發(fā)現(xiàn)兩次從i讀數(shù)據(jù)的代碼之間的代碼沒有對(duì)i進(jìn)行過操作,它會(huì)自動(dòng)把上次讀的數(shù)據(jù)放在b中族沃。而不是重新從i里面讀频祝。這樣以來,如果i是一個(gè)寄存器變量或者表示一個(gè)端口數(shù)據(jù)就容易出錯(cuò)脆淹,所以說volatile可以保證對(duì)特殊地址的穩(wěn)定訪問常空。
注意,在vc6中盖溺,一般調(diào)試模式?jīng)]有進(jìn)行代碼優(yōu)化漓糙,所以這個(gè)關(guān)鍵字的作用看不出來。下面通過插入?yún)R編代碼烘嘱,測(cè)試有無volatile關(guān)鍵字昆禽,對(duì)程序最終代碼的影響:
首先,用classwizard建一個(gè)win32 console工程蝇庭,插入一個(gè)voltest.cpp文件醉鳖,輸入下面的代碼:
#include <stdio.h>
void main()
{
int i=10;
int a = i;
printf("i= %d\n",a);
//下面匯編語句的作用就是改變內(nèi)存中i的值,但是又不讓編譯器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
}
然后遗契,在調(diào)試版本模式運(yùn)行程序辐棒,輸出結(jié)果如下:
i = 10
i = 32
然后病曾,在release版本模式運(yùn)行程序牍蜂,輸出結(jié)果如下:
i = 10
i = 10
輸出的結(jié)果明顯表明,release模式下泰涂,編譯器對(duì)代碼進(jìn)行了優(yōu)化鲫竞,第二次沒有輸出正確的i值。
下面逼蒙,我們把 i的聲明加上volatile關(guān)鍵字从绘,看看有什么變化:
#include <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf("i= %d\n",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf("i= %d\n",b);
}
分別在調(diào)試版本和release版本運(yùn)行程序,輸出都是:
i = 10
i = 32
這說明這個(gè)關(guān)鍵字發(fā)揮了它的作用是牢!
關(guān)鍵字volatile有什么含意?并給出三個(gè)不同的例子僵井。
一個(gè)定義為volatile的變量是說這變量可能會(huì)被意想不到地改變,這樣驳棱,編譯器就不會(huì)去假設(shè)這個(gè)變量的值了批什。精確地說就是,優(yōu)化器在用到這個(gè)變量時(shí)必須每次都小心地重新讀取這個(gè)變量的值社搅,而不是使用保存在寄存器里的備份驻债。 下面是volatile變量的幾個(gè)例子:
- 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
- 一個(gè)中斷服務(wù)子程序中會(huì)訪問到的非自動(dòng)變量(Non-automatic variables)
- 多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量
搞嵌入式的家伙們經(jīng)常同硬件乳规、中斷、RTOS等等 打交道合呐,所有這些都要求用到volatile變量暮的。不懂得volatile的內(nèi)容將會(huì)帶來災(zāi)難。
1)一個(gè)參數(shù)既可以是const還可以是 volatile嗎淌实?解釋為什么冻辩。
2); 一個(gè)指針可以是volatile 嗎?解釋為什么拆祈。
3); 下面的函數(shù)有什么錯(cuò)誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的微猖。例如只讀的狀態(tài)寄存器。
它是volatile因?yàn)樗赡鼙灰庀氩坏降馗淖儭?/p>
它是const因?yàn)槌绦虿粦?yīng)該試圖 去修改它缘屹。
2)是的凛剥。
盡管這并不很常見。一個(gè)例子是當(dāng)一個(gè)中服務(wù)子程序修該一個(gè)指向一個(gè)buffer的指針時(shí)轻姿。
這段代碼有點(diǎn)變態(tài)犁珠。
這段代碼的目的是用來返指針ptr指向值的平方,但是互亮,由于ptr指向一個(gè)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;
}