【嵌入式】C语言中volatile关键字
00. 目錄
文章目錄
- 00. 目錄
- 01. volatile概述
- 02. volatile應用場景
- 03. volatile應用示例
- 04. 嵌入式系統中應用
- 05. volatile官方說明
- volatile
- 06. 附錄
01. volatile概述
volatile是C語言中的一個關鍵字。將變量定義為volatile就表示告訴編譯器這個變量可能會被竟想不到地改變,在這種情況下,編譯器就不會去假設這個變量的值了,及優化器在用到這個變量是必須每次重新讀取他的值。
02. volatile應用場景
在程序中,volatile變量常用于以下幾種情況:
- 并行設備的硬件寄存器(例如:狀態寄存器)
- 在中斷服務子程序中會訪問到的非自動變量(即全局變量)
- 多線程應用中被幾個任務共享的變量
對于一般變量,其存儲的位置是在內存中,但也有可能存儲在處理器的寄存器中。在程序中,只要寄存器的內容沒有被更改,對變量訪問就不需要訪問內存,只需要直接使用寄存器的變量。
例如:在程序中可以按照以下的形式定義volatile變量:
void test() {volatile char temp; }當變量temp被定義成volatile類型的時候,它就不會被編譯器優化,在每次訪問temp變量的時候都將重新在內存中讀取它的值。
事實上,在編譯器的優化中,類似temp這種建立在函數棧上的變量是不太可能被外部更改的。在程序中,一般容易被更改的變量是指針指向的內容。
03. volatile應用示例
C語言編譯器一般都有優化的功能,對代碼進行優化。例如:
int tmp, a1, a2; tmp = (unsigned int *)0x4004; a1 = *tmp; a2 = *tmp;在某些編譯器中,這段代碼很可能被編譯器優化,優化的結果等同如下代碼。
int tmp, a1, a2; tmp = (unsigned int *)0x4004; a1 = *tmp; a2 = a1;這種優化在一般的情況下沒有什么錯誤,但是在特殊的情況下卻可能引發錯誤。例如:第一次讀操作(a1 = *tmp)后,*tmp的內容有可能已經被更新,在這種情況下,第2次讀操作讀出的內容與第一次不一樣。原本程序的含義也是在兩個不同的時刻讀出兩個不同的值,但是經過優化后的程序只能讀出相同的值。這就需要使用volatile關鍵字。上述的程序英嘎攜程如下形式:
volatile unsigned int *tmp; int a1, a2; tmp = (volatile unsigned int *)0x4004; a1 = *tmp; a2 = *tmp;總結
volatile在嵌入式系統中普通用于可能具有并行操作性質的數據,這些變量可能是被外部改變或者內部并行的程序改變。
04. 嵌入式系統中應用
在程序中對GPIO相關寄存器的定義
#define PINSEL0 (*((volatile unsigned long *) 0xE002C000)) #define PINSEL1 (*((volatile unsigned long *) 0xE002C004)) #define PINSEL2 (*((volatile unsigned long *) 0xE002C008)) #define PINSEL3 (*((volatile unsigned long *) 0xE002C00C))寄存器的定義應該用volatile修飾,避免其在編譯過程中被編譯器優化,產生意想不到的后果。
05. volatile官方說明
原文如下
volatile
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.
表明變量能被后臺程序修改
關鍵字volatile和const是完全相反的。它表明變量可能會通過某種方式發生改變,而這種方式是你通過分析正常的程序流程完全預測不出來的。(例如,一個變量可能被中斷處理程序修改)。關鍵字使用語法如下:
volatile data-definition;每次對變量內容的引用會重新從內存中加載而不是從變量在寄存器里面的拷貝加載。
我的理解:以中斷處理程序修改變量解釋可能不太合適,以GPIO為例最合適。首先什么是變量?變量就是一塊編了地址的內存區域。GPIO的數據寄存器有一個地址,大小一般為32bit,所以這個數據寄存器可以認為就是一個變量,我們可以讀寫它。如果GPIO設置為輸入,修改GPIO數據寄存器這個變量的就是這個GPIO的引腳,不管你如何分析你的程序,你不可能知道這個GPIO數據寄存器里面值是多少,你得讀它。你此刻讀到數據和下一刻讀到的完全可能是不一樣的。簡單的說就是你要的數據不同步。使用volatile修飾后,會強制你每次引用GPIO寄存器對應的變量時都會去它的寄存器里面讀。
防止編譯器優化掉操作變量的語句
a.c文件
int main(void) {volatile char a;a = 5;a = 7;return 0; }b.c文件
int main(void) {char a;a = 5;a = 7;return 0; }編譯生成匯編文件
deng@itcast:~/tmp$ arm-linux-gcc -S a.c -o a.s deng@itcast:~/tmp$ arm-linux-gcc -S b.c -o b.s deng@itcast:~/tmp$ diff a.s b.s 12c12 < .file "a.c" --- > .file "b.c" deng@itcast:~/tmp$發現兩個匯編文件相差不大,接下來調整優化等級
編譯生成匯編文件
deng@itcast:~/tmp$ arm-linux-gcc -O3 -S b.c -o b.s deng@itcast:~/tmp$ arm-linux-gcc -O3 -S a.c -o a.s deng@itcast:~/tmp$ diff a.s b.s 12c12 < .file "a.c" --- > .file "b.c" 18c18 < @ args = 0, pretend = 0, frame = 8 --- > @ args = 0, pretend = 0, frame = 0 21,24d20 < sub sp, sp, #8 < mov r3, #5 < strb r3, [sp, #4] < mov r3, #7 26,27d21 < strb r3, [sp, #4] < add sp, sp, #8 deng@itcast:~/tmp$可以看到未加volatile修飾的文件b.c,在優化后,匯編對應的a=5;a=7;這兩個語句直接優化沒了。a=1;a=0;假設a是控制GPIO的語句,原來打算是讓GPIO先拉高,再拉低,實現某種時序,結果優化一開,這兩句直接廢了。這讓你在調試硬件的時候會感到莫名其妙。所以這種情況得像a.c那樣用volatile來修飾。
防止編譯器優化變量的存取對象(memory or register)
a.c文件
int main(void) {int b;int c;volatile int* a = (int*)0x30000000;b = *a;c = *a;return c + b; }b.c文件
int main(void) {int b;int c;int* a = (int*)0x30000000;b = *a;c = *a;return c + b; }生成對應的a.s文件
mov r3, #805306368 ldr r2, [r3] @ b = *a; ldr r0, [r3] @ c = *a; add r0, r0, r2 @ b + c; bx lr生成對應的b.s文件
mov r3, #805306368 ldr r0, [r3] @ b = *a; mov r0, r0, asl #1 @ b << 2; 也就是 b * 2;也就是 b + b;也就是 add r0, r0, r0(可能這句匯編不合法) bx lr可以看到b.s被優化后,第一次取*a的值時,是從地址0x30000000取出來的(ldr r0, [r3]),第二次就直接沒取了,是直接使用了r0的值。這里的r0就是*a的緩存。
訪問被volatile修飾的變量時,強制訪問內存中的值,而不是緩存中的。
volatile關鍵詞影響編譯器編譯的結果,用volatile聲明的變量表示該變量隨時可能發生變化,與該變量有關的運算,不要進行編譯優化,以免出錯
int main(void) {int b;volatile int* a = (int*)0x30000000;b = (*a) * (*a);return b; }生成對應的匯編
mov r3, #805306368 ldr r2, [r3] ① ldr r0, [r3] ② mul r0, r2, r0 bx lr程序本意是要計算平方。如果這段代碼在運行至①這行匯編時,被調度開了,過了一陣調度回來繼續運行②行,此時完全有可能 R2 != R0。那么計算出來的結果R0必然不等于那個平方值。
06. 附錄
6.1 volatile官方描述
總結
以上是生活随笔為你收集整理的【嵌入式】C语言中volatile关键字的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【嵌入式】C语言程序调试和宏使用的技巧
- 下一篇: 【IT资讯】Linux Kernel 5