日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

C语言再学习 -- 关键字volatile

發(fā)布時(shí)間:2025/3/15 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C语言再学习 -- 关键字volatile 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

上周確實(shí)事情挺多的,年會(huì)、公司聚餐,一到過年就有忙不完的事分心。還好C語言再學(xué)習(xí)總結(jié)的已經(jīng)差不多了,年前也不展開別的了,接下來這十幾天、總結(jié)幾篇典型的面試題吧。

言歸正傳,接下來看看關(guān)鍵字 volatile。


一、volatile 介紹

參看:volatile詳解

參看:C Language Keywords

Indicates that a variable can be changed by a background routine.

Keyword?volatile?is an extreme opposite of?const.It indicates that a variable may be changed in a way which is absolutely unpredictable by analysing the normal program flow (for example, a variable which may be changed by an interrupt handler). This keyword uses the following syntax:

volatile data-definition;

Every reference to the variable will reload the contents from memory rather than take advantage of situations where a copy can be in a register.

翻譯:

表示一個(gè)變量也許會(huì)被后臺(tái)程序改變,關(guān)鍵字 volatile 是與 const 絕對對立的。它指示一個(gè)變量也許會(huì)被某種方式修改,這種方式按照正常程序流程分析是無法預(yù)知的(例如,一個(gè)變量也許會(huì)被一個(gè)中斷服務(wù)程序所修改)。這個(gè)關(guān)鍵字使用下列語法定義:

volatile data-definition;

變量如果加了 volatile 修飾,則會(huì)從內(nèi)存重新裝載內(nèi)容,而不是直接從寄存器拷貝內(nèi)容。?


volatile應(yīng)用比較多的場合,在中斷服務(wù)程序和cpu相關(guān)寄存器的定義

示例:

volatile 用于相關(guān)寄存器定義

//編譯led.c文件#define GPC1CON *((volatile unsigned int*)0xE0200080) #define GPC1DAT *((volatile unsigned int*)0xE0200084) #define GPC1PUD *((volatile unsigned int*)0xE0200088) //隱式聲明 void delay (unsigned int); void led_test (void) {//配置相應(yīng)管腳為輸出功能 GPC1_3GPC1CON &= ~(0x0f << 12);GPC1CON |= (1 << 12);//GPC1_4為輸出功能GPC1CON |= (1 << 16);//禁止內(nèi)部上拉下拉功能GPC1PUD &= ~(0x03 << 6);GPC1PUD &= ~(0x03 << 8);while (1) {//燈亮GPC1DAT |= (1 << 3);GPC1DAT |= (1 << 4);delay (0x100000);//燈滅GPC1DAT &= ~(1 << 3);GPC1DAT &= ~(1 << 4);delay (0x100000);} }void delay (unsigned int n) {unsigned int i = 0;for (i = n; i != 0; i--); }編譯: arm-linux-gcc -c led.c -o led.o –nostdlib 不使用標(biāo)準(zhǔn)庫,生成led.o文件


二、為什么使用 volatile

我們上一篇文章講到了 const 和 volatile 關(guān)鍵字是一種類型修飾符。volatile 的作用?是作為指令關(guān)鍵字,確保本條指令不會(huì)因編譯器的優(yōu)化而省略,且要求每次直接讀值。


現(xiàn)在考慮一個(gè)問題,編譯器如何對代碼進(jìn)行優(yōu)化的?

我們看一個(gè)例子:

//示例一 #include <stdio.h> int main (void) {int i = 10;int a = i; //優(yōu)化int b = i;printf ("i = %d\n", b);return 0; } //編譯優(yōu)化、查看匯編 gcc -O2 -S test.c cat test.s .file "test.c".section .rodata.str1.1,"aMS",@progbits,1 .LC0:.string "i = %d\n".section .text.startup,"ax",@progbits.p2align 4,,15.globl main.type main, @function main: .LFB22:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5andl $-16, %espsubl $16, %espmovl $10, 8(%esp)movl $.LC0, 4(%esp)movl $1, (%esp)call __printf_chkxorl %eax, %eaxleave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc .LFE22:.size main, .-main.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3".section .note.GNU-stack,"",@progbits
//示例二 #include <stdio.h> int main (void) {volatile int i = 10;int a = i; //未優(yōu)化int b = i;printf ("i = %d\n", b);return 0; } //編譯優(yōu)化、查看匯編 gcc -O2 -S test.c cat test.s .file "test.c".section .rodata.str1.1,"aMS",@progbits,1 .LC0:.string "i = %d\n".section .text.startup,"ax",@progbits.p2align 4,,15.globl main.type main, @function main: .LFB22:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5andl $-16, %espsubl $32, %espmovl $10, 28(%esp)movl 28(%esp), %eaxmovl 28(%esp), %eaxmovl $.LC0, 4(%esp)movl $1, (%esp)movl %eax, 8(%esp)call __printf_chkxorl %eax, %eaxleave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc .LFE22:.size main, .-main.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3".section .note.GNU-stack,"",@progbits

比較:



可以清楚的看到:使用 volatile 的代碼編譯未優(yōu)化。

volatile 指出 i 是隨時(shí)可能發(fā)生變化的,每次使用它的時(shí)候必須從 i的地址中讀取,因而編譯器生成的匯編代碼會(huì)重新從i的地址讀取數(shù)據(jù)放在 b 中。而優(yōu)化做法是,由于編譯器發(fā)現(xiàn)兩次從 i讀數(shù)據(jù)的代碼之間的代碼沒有對 i 進(jìn)行過操作,它會(huì)自動(dòng)把上次讀的數(shù)據(jù)放在 b 中。而不是重新從 i 里面讀。這樣以來,如果 i是一個(gè)寄存器變量或者表示一個(gè)端口數(shù)據(jù)就容易出錯(cuò),所以說 volatile 可以保證對特殊地址的穩(wěn)定訪問。

如果上述例子,還是不夠明顯:

#include <stdio.h> #include <sys/timeb.h> long long getSystemTime() { struct timeb t; ftime(&t); return 1000 * t.time + t.millitm; } #define TIME 1000000000 int main(void) { volatile int a, b = TIME; /* volatile修飾變量 */ int x, y = TIME; /* 一般變量 */ long long start = 0, end = 0; start=getSystemTime(); for (a = 0; a < b; a++); end=getSystemTime(); printf("vloatile修飾變量用時(shí): %lld ms\n", end - start); start=getSystemTime(); for (x = 0; x < y; x++); end=getSystemTime(); printf("一般變量用時(shí): %lld ms\n", end - start); return 0; } ? 編譯:gcc test.c 輸出結(jié)果: vloatile修飾變量用時(shí): 3738 ms 一般變量用時(shí): 3742 ms 優(yōu)化編譯:gcc -O2 test.c 輸出結(jié)果: vloatile修飾變量用時(shí): 3550 ms 一般變量用時(shí): 0 ms


可明顯看出:

for(int i=0; i<100000; i++);
這個(gè)語句用來測試空循環(huán)的速度的,但是編譯器肯定要把它優(yōu)化掉,根本就不執(zhí)行。
如果你寫成,
for(volatile int i=0; i<100000; i++);

它就會(huì)執(zhí)行了。


我們用上面的例子基本已經(jīng)搞明白,volatile 不會(huì)被編譯器優(yōu)化了,現(xiàn)在講點(diǎn)理論知識(shí)。

參看:C語言中volatile關(guān)鍵字的作用


1、編譯器優(yōu)化介紹:
由于內(nèi)存訪問速度遠(yuǎn)不及CPU處理速度
,為提高機(jī)器整體性能,在硬件上引入硬件高速緩存Cache,加速對內(nèi)存的訪問。另外在現(xiàn)代CPU中指令的執(zhí)行并不一定嚴(yán)格按照順序執(zhí)行,沒有相關(guān)性的指令可以亂序執(zhí)行,以充分利用CPU的指令流水線,提高執(zhí)行速度。以上是硬件級(jí)別的優(yōu)化。再看軟件一級(jí)的優(yōu)化:一種是在編寫代碼時(shí)由程序員優(yōu)化,另一種是由編譯器進(jìn)行優(yōu)化。編譯器優(yōu)化常用的方法有:將內(nèi)存變量緩存到寄存器;調(diào)整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。對常規(guī)內(nèi)存進(jìn)行優(yōu)化的時(shí)候,這些優(yōu)化是透明的,而且效率很好。由編譯器優(yōu)化或者硬件重新排序引起的問題的解決辦法是在從硬件(或者其他處理器)的角度看必須以特定順序執(zhí)行的操作之間設(shè)置內(nèi)存屏障(memory barrier),Linux 提供了一個(gè)宏解決編譯器的執(zhí)行順序問題。
void Barrier(void)
這個(gè)函數(shù)通知編譯器插入一個(gè)內(nèi)存屏障,但對硬件無效,編譯后的代碼會(huì)把當(dāng)前CPU寄存器中的所有修改過的數(shù)值存入內(nèi)存,需要這些數(shù)據(jù)的時(shí)候再重新從內(nèi)存中讀出。

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

volatile的本意是“易變的” 因?yàn)樵L問寄存器要比訪問內(nèi)存單元快的多,所以編譯器一般都會(huì)作減少存取內(nèi)存的優(yōu)化,但有可能會(huì)讀臟數(shù)據(jù)。當(dāng)要求使用volatile聲明變量值的時(shí)候,系統(tǒng)總是重新從它所在的內(nèi)存讀取數(shù)據(jù),即使它前面的指令剛剛從該處讀取過數(shù)據(jù)。精確地說就是,遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問;如果不使用valatile,則編譯器將對所聲明的語句進(jìn)行優(yōu)化。(簡潔的說就是:volatile關(guān)鍵詞影響編譯器編譯的結(jié)果,用volatile聲明的變量表示該變量隨時(shí)可能發(fā)生變化,與該變量有關(guān)的運(yùn)算,不要進(jìn)行編譯優(yōu)化,以免出錯(cuò)


三、volatile 使用

1、并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)

存儲(chǔ)器映射的硬件寄存器通常也要加 voliate,因?yàn)槊看螌λ淖x寫都可能有不同意義。

例如:
假設(shè)要對一個(gè)設(shè)備進(jìn)行初始化,此設(shè)備的某一個(gè)寄存器為0xff800000。

int *output = (unsigned int *)0xff800000;//定義一個(gè)IO端口; int init(void) {int i;for(i=0;i< 10;i++){*output = i; } } 經(jīng)過編譯器優(yōu)化后,編譯器認(rèn)為前面循環(huán)半天都是廢話,對最后的結(jié)果毫無影響,因?yàn)樽罱K只是將output這個(gè)指針賦值為 9,所以編譯器最后給你編譯編譯的代碼結(jié)果相當(dāng)于:

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


再例如上面提到的?volatile 用于相關(guān)寄存器定義

//編譯led.c文件#define GPC1CON *((volatile unsigned int*)0xE0200080) #define GPC1DAT *((volatile unsigned int*)0xE0200084) #define GPC1PUD *((volatile unsigned int*)0xE0200088) //隱式聲明 void delay (unsigned int); void led_test (void) {//配置相應(yīng)管腳為輸出功能 GPC1_3GPC1CON &= ~(0x0f << 12);GPC1CON |= (1 << 12);//GPC1_4為輸出功能GPC1CON |= (1 << 16);//禁止內(nèi)部上拉下拉功能GPC1PUD &= ~(0x03 << 6);GPC1PUD &= ~(0x03 << 8);while (1) {//燈亮GPC1DAT |= (1 << 3);GPC1DAT |= (1 << 4);delay (0x100000);//燈滅GPC1DAT &= ~(1 << 3);GPC1DAT &= ~(1 << 4);delay (0x100000);} }void delay (unsigned int n) {unsigned int i = 0;for (i = n; i != 0; i--); }編譯: arm-linux-gcc -c led.c -o led.o –nostdlib 不使用標(biāo)準(zhǔn)庫,生成led.o文件
#define GPC1CON *((volatile unsigned int*)0xE0200080) ? 怎么理解?

這里其實(shí)就是定義了一個(gè)指針變量。

GPC1CON 為寄存器名稱、0xE0200080 為寄存器地址、(volatile unsigned int*) 為強(qiáng)制類型轉(zhuǎn)換。

我們知道 volatile 和 const 一樣為類型修飾符,不改變變量類型。


寄存器地址為什么要加 volatile 修飾呢?

是因?yàn)?#xff0c;這些寄存器里面的值是隨時(shí)變化的。如果我們沒有將這個(gè)地址強(qiáng)制類型轉(zhuǎn)換成 volatile,那么我們在使用GPC1CON 這個(gè)寄存器的時(shí)候,?會(huì)直接從 CPU 的寄存器中取值。因?yàn)橹?span style="font-family:'Microsoft YaHei'; font-size:18px">GPC1CON ?被訪問過,也就是之前就從內(nèi)存中取出?GPC1CON 的值保存到某個(gè)寄存器中。之所以直接從寄存器中取值,而不去內(nèi)存中取值,是因?yàn)榫幾g器優(yōu)化代碼的結(jié)果(訪問 CPU寄存器比訪問 RAM 快的多)。用 volatile 關(guān)鍵字對?0xE0200080 ?進(jìn)行強(qiáng)制轉(zhuǎn)換,使得每一次訪問?GPC1CON 時(shí),執(zhí)行部件都會(huì)從?0xE0200080 ?這個(gè)內(nèi)存單元中取出值來賦值給?GPC1CON ?。


2、一個(gè)中斷服務(wù)子程序中會(huì)訪問到的非自動(dòng)變量(Non-automatic variables)

由于訪問寄存器的速度要快過RAM,所以編譯器一般都會(huì)作減少存取外部RAM的優(yōu)化,例如:

static int i=0; //i 為非自動(dòng)變量 int main(void) {...while (1){ if (i) dosomething(); } } /* Interrupt service routine. */ void ISR_2(void) {i=1; }

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


3、多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量

當(dāng)兩個(gè)線程都要用到某一個(gè)變量且該變量的值會(huì)被改變時(shí),應(yīng)該用 volatile 聲明,該關(guān)鍵字的作用是防止優(yōu)化編譯器把變量從內(nèi)存裝入CPU寄存器中。如果變量被裝入寄存器,那么兩個(gè)線程有可能一個(gè)使用內(nèi)存中的變量,一個(gè)使用寄存器中的變量,這會(huì)造成程序的錯(cuò)誤執(zhí)行。volatile的意思是讓編譯器每次操作該變量時(shí)一定要從內(nèi)存中真正取出,而不是使用已經(jīng)存在寄存器中的值,如下:

volatile BOOL bStop = FALSE; //bStop 為共享全局變量 (1) 在一個(gè)線程中: while( !bStop ) { ... } bStop = FALSE; return; (2) 在另外一個(gè)線程中,要終止上面的線程循環(huán): bStop = TRUE; while( bStop );

等待上面的線程終止,如果bStop不使用volatile申明,那么這個(gè)循環(huán)將是一個(gè)死循環(huán),因?yàn)閎Stop已經(jīng)讀取到了寄存器中,寄存器中bStop的值永遠(yuǎn)不會(huì)變成FALSE,加上volatile,程序在執(zhí)行時(shí),每次均從內(nèi)存中讀出bStop的值,就不會(huì)死循環(huán)了。


四、volatile 問題和總結(jié)

volatile 常見的幾個(gè)面試題

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

可以,例如只讀的狀態(tài)寄存器。它是 volatile 因?yàn)樗赡鼙灰庀氩坏降馗淖儭K?const 因?yàn)?程序不應(yīng)該試圖去修改它。

2、一個(gè)指針可以是 volatile 嗎?

可以,當(dāng)一個(gè)中服務(wù)子程序修改一個(gè)指向一個(gè) buffer 的指針時(shí)。

3、下面的函數(shù)有什么錯(cuò)誤:?

int square(volatile int *ptr) { return *ptr * *ptr; } 這段代碼的目的是用來返指針*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; }

總結(jié):

volatile 關(guān)鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改。volatile 提醒編譯器它后面所定義的變量隨時(shí)都有可能改變,因此編譯后的程序每次需要存儲(chǔ)或讀取這個(gè)變量的時(shí)候,都會(huì)直接從變量地址中讀取數(shù)據(jù)。如 果沒有 volatile 關(guān)鍵字,則編譯器可能優(yōu)化讀取和存儲(chǔ),可能暫時(shí)使用寄存器中的值,如果這個(gè)變量由別的程序更新了的話,將出現(xiàn)不一致的現(xiàn)象。所以遇到這個(gè)關(guān)鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進(jìn)行優(yōu)化,從而可以提供對特殊地址的穩(wěn)定訪問。

與50位技術(shù)專家面對面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖

總結(jié)

以上是生活随笔為你收集整理的C语言再学习 -- 关键字volatile的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。