C語言中volatile關鍵字詳解以及常見的面試問題

2021-05-08 18:00:02

編譯器的優化

    程式執行的優化可以分為硬體和軟體。硬體上是在CPU和記憶體中間增加cache,來解決CPU和記憶體之間執行速率差異過大的問題。軟體上則分為編譯器優化和程式設計師優化:程式設計師優化是程式設計師在編寫程式碼時,對程式碼的邏輯順序進行合理安排,提升效率;編譯器優化則是程式設計師寫好的程式碼,在編譯連結時由編譯器進行優化,會調整程式碼的執行順序或者刪掉一些無用的語句。
    編譯器優化常用的方法有:將記憶體變數快取到暫存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。因為CPU和記憶體的讀寫速度差異過大,所以編譯器會盡量減少讀寫記憶體的操作,比如把中間變數或者剛讀取的資料存在cache、暫存器中。編譯器有一種技術叫做資料流分析,分析程式中的變數在哪裡賦值、在哪裡使用、在哪裡失效,分析結果可以用於常數合併,常數傳播等優化,進一步可以消除一些程式碼,換句話說就是程式設計師寫的程式碼不是都會執行的,有時候編譯器會刪除掉一些編譯器認為無意義的程式碼。

C語言中volatile關鍵字的作用

volatile int a;
main()
{
	a=0;
	b = a;
	printf("b = %d\n", b);
}

volatile的意思是「多變的」,被volatile修飾的變數則表示該變數會被意想不到的改變,每次使用到該變數時都要去該變數的記憶體地址處讀取,而不要去使用暫存的該變數的值。比如上面的程式碼,當執行b=a語句時,編譯器會判斷在a賦值以後就沒有改變a的值,因此會把暫存器中暫存的a的值給b,而不是去a的記憶體地址處去讀取a再賦值給b。一般情況這樣是沒問題的,但是有中斷程式或者別的程序去修改了a的值,那暫存器裡暫存的a就和記憶體裡存放的a不一致了。不加volatile修飾,編譯器還是會將暫存器暫存的a賦值給b,此時暫存器裡a的值已經不再是最新的a的值,賦值是有問題的;加了volatile修飾,每次使用a時都會去a的記憶體地址處讀取a,這樣能保證每次讀取的a都是最新的值,但是會導致效率降低,因為讀取記憶體的速度遠低於讀取暫存器的速度。簡單來說:加了volatile修飾,每次使用該變數都要去記憶體地址處讀取,程式設計師寫的每一行程式碼都要執行,不要編譯器做優化,因為編譯器在優化時會將編譯器認為無用的程式碼刪掉。

volatile關鍵字的應用範例:

1.中斷服務程式中修改的某個變數值是供其他程式檢測的狀態值

static int status;

int main(void)

{
     while (1)
     {
		if (status) 
		{
			printf("status = 1\n");
			status = 0;
		}
	}
}
//中斷服務程式
void ISR_fun(void)

{
      status=1;
}

程式碼解析:在上面的程式中,中斷服務程式會改變status的值,目的是讓主程式列印一次"status = 1\n"。主程式並不知道會有中斷服務程式去修改status的值,經過編譯器優化後,主程式第一次讀取status的值是從記憶體處讀取,以後使用status值時都用的之前讀取的那份;那中斷服務程式就算執行並改變了status的值,主程式也不會再去記憶體處讀取,在主程式中status的值永遠不會變,這樣程式就出錯了。如果加上volatile修飾status變數,那主程式每次使用status值時都會去記憶體地址處讀取,這樣就能保證中斷服務程式修改status的值後,能真正的起到作用。

2.操作硬體裝置的某些暫存器時需要加volatile修飾:

volatile int  *sequenceInit = (unsigned  int *)0xfffff000;//定義一個暫存器操作指標

int   init(void)

{
      int i;
      for(i=0;i< 10;i++)
      {
         *sequenceInit = i;
	}
}

程式碼解析:ARM晶片是統一編址,操作暫存器就和讀取記憶體地址是一樣的,假設上面的程式是ARM晶片對記憶體的初始化程式碼。ARM晶片初始化記憶體就是通過操作記憶體控制器去初始化記憶體,按照資料手冊的初始化說明向記憶體控制器的暫存器依次寫入特定值。假設for迴圈是時序的初始化,要依次向地址為0xfffff000的暫存器寫入0到9。如果不加volatile修飾,編譯器會認為依次向暫存器寫入0到9和直接寫入9效果是一樣的,那整個for迴圈會被替換成sequenceInit =9,顯然這樣是不能初始化記憶體的,初始化會失敗。以上分析可知,此時的sequenceInit 必須加volatile修飾。

volatile常見的面試問題:

1.一個引數既可以是const還可以是volatile嗎?

可以的,例如唯讀的狀態暫存器。它是volatile因為它可能被意想不到地改變。它是const因為程式不應該試圖去修改它。

2.一個指標可以是volatile 嗎?

可以,當一箇中服務子程式修該一個指向一個buffer的指標時。指標是一種普通的變數,從存取上沒有什麼不同於其他變數的特性。其儲存的數值是個整形資料,和整型變數不同的是,這個整型資料指向的是一段記憶體地址。