這個問題恰好我在我之前的文章中說過。
現代C語言編譯器已經非常聰明,為了保證程式的執行效率,會在編譯時對程式碼做最佳化。水平較低的程式設計師寫出的程式碼比較臃腫,編譯器的最佳化確能夠增加程式的執行效率。但是,編譯器有時“聰明過了頭”,自以為是的把有用的語句最佳化掉了,反而導致程式不能正常工作。
例如下面這幾句程式碼,
編譯器很聰明,會認為 “a = 0x10” 這句沒有意義,因為我們並沒有使用 a 的 0x10 這個值,在下一句就被 0x08 覆蓋了。所以在編譯時,編譯器直接就忽略了 “a = 0x10” 這句,這樣的確會增加效率,但是在某些情況下,即使只是賦值,也是有意義的。
還記得我在《程式設計師編寫的程式碼為什麼可以控制計算機硬體工作?》一節中提到的電燈開關的例子嗎?我們用 0 表示滅燈,用 1 表示開燈。
上圖的電燈狀態顯然可以用 01000 表示。
現在假設我們用變數 a 表示電燈的狀態,那麼“a = 0x10” 這句就表示開最左邊的燈,它是有意義的。
本來電燈管理員想先開一下最左邊的燈,看看它亮不亮,有沒有壞掉,那現在編譯器把這句忽略了,最左邊的燈即使沒壞也不亮,會害的電燈管理員花很多時間檢查電路。
這就是編譯器“自作聰明”帶來的問題。那我只能不讓編譯器做優化了嗎?答案是沒有必要因為這點小問題,就全盤否定編譯器最佳化帶來的效率提升。C99 提供了 關鍵字,就是專門用來解決這樣的問題的。的字面意思是“不穩定的,易變的”,它可以用來修飾變數,以此來告訴編譯器,別最佳化我。編譯器看到以後,就知道下面這幾行程式碼不需要最佳化,不管自己看著多麼不爽,也照樣執行,不做任何最佳化:
使用了 volatile 關鍵字修飾代表電燈開關的變數 a,編譯後,會發現最左邊的電燈也亮了一下。電燈管理員知道它沒壞,可以安心了。
你可能會說,我程式設計不涉及硬體,這個關鍵字用不到。可是,即使如此,編譯器的最佳化,有時候也會讓人覺得很困惑。請看下面這段程式碼:main 函式一直在判斷變數 的值是否變為 0,如果變了,就在終端打印出 “hello world”,否則就一直等下去。 函式在一秒後,將 的值修改為 0。
可以看出,原本程式設計師的意圖是透過變數 控制 main 打印出 “hello world” 的時機(這裡是1秒後列印)。我們編譯它,執行會發現,程式一直不列印“hello world”,這很奇怪。我們一起來看看編譯後,程式的彙編程式碼:
這就明白了,編譯器“自作聰明”的優化了程式碼,導致程式沒有按照程式設計師的意圖執行。
編譯器認為:在 main 函式中,沒有任何對 的修改,while() 也是什麼都沒做,所以只需要判斷 最開始的值即可,不需要再從記憶體重新讀取 。
接下來,我們在“int is_continue = 1;”前加上 “volatile”關鍵字,其他的程式碼都不變,再編譯執行,發現 1 秒後,終端有“hello world”列印。檢視相應的彙編程式碼:編譯器見到“volatile”關鍵字後,不再自作主張,每次都從記憶體讀取 再做判斷,程式終於與預期一致了。
這個問題恰好我在我之前的文章中說過。
現代C語言編譯器已經非常聰明,為了保證程式的執行效率,會在編譯時對程式碼做最佳化。水平較低的程式設計師寫出的程式碼比較臃腫,編譯器的最佳化確能夠增加程式的執行效率。但是,編譯器有時“聰明過了頭”,自以為是的把有用的語句最佳化掉了,反而導致程式不能正常工作。
例如下面這幾句程式碼,
編譯器很聰明,會認為 “a = 0x10” 這句沒有意義,因為我們並沒有使用 a 的 0x10 這個值,在下一句就被 0x08 覆蓋了。所以在編譯時,編譯器直接就忽略了 “a = 0x10” 這句,這樣的確會增加效率,但是在某些情況下,即使只是賦值,也是有意義的。
還記得我在《程式設計師編寫的程式碼為什麼可以控制計算機硬體工作?》一節中提到的電燈開關的例子嗎?我們用 0 表示滅燈,用 1 表示開燈。
上圖的電燈狀態顯然可以用 01000 表示。
現在假設我們用變數 a 表示電燈的狀態,那麼“a = 0x10” 這句就表示開最左邊的燈,它是有意義的。
本來電燈管理員想先開一下最左邊的燈,看看它亮不亮,有沒有壞掉,那現在編譯器把這句忽略了,最左邊的燈即使沒壞也不亮,會害的電燈管理員花很多時間檢查電路。
這就是編譯器“自作聰明”帶來的問題。那我只能不讓編譯器做優化了嗎?答案是沒有必要因為這點小問題,就全盤否定編譯器最佳化帶來的效率提升。C99 提供了 關鍵字,就是專門用來解決這樣的問題的。的字面意思是“不穩定的,易變的”,它可以用來修飾變數,以此來告訴編譯器,別最佳化我。編譯器看到以後,就知道下面這幾行程式碼不需要最佳化,不管自己看著多麼不爽,也照樣執行,不做任何最佳化:
使用了 volatile 關鍵字修飾代表電燈開關的變數 a,編譯後,會發現最左邊的電燈也亮了一下。電燈管理員知道它沒壞,可以安心了。
你可能會說,我程式設計不涉及硬體,這個關鍵字用不到。可是,即使如此,編譯器的最佳化,有時候也會讓人覺得很困惑。請看下面這段程式碼:main 函式一直在判斷變數 的值是否變為 0,如果變了,就在終端打印出 “hello world”,否則就一直等下去。 函式在一秒後,將 的值修改為 0。
可以看出,原本程式設計師的意圖是透過變數 控制 main 打印出 “hello world” 的時機(這裡是1秒後列印)。我們編譯它,執行會發現,程式一直不列印“hello world”,這很奇怪。我們一起來看看編譯後,程式的彙編程式碼:
這就明白了,編譯器“自作聰明”的優化了程式碼,導致程式沒有按照程式設計師的意圖執行。
編譯器認為:在 main 函式中,沒有任何對 的修改,while() 也是什麼都沒做,所以只需要判斷 最開始的值即可,不需要再從記憶體重新讀取 。
接下來,我們在“int is_continue = 1;”前加上 “volatile”關鍵字,其他的程式碼都不變,再編譯執行,發現 1 秒後,終端有“hello world”列印。檢視相應的彙編程式碼:編譯器見到“volatile”關鍵字後,不再自作主張,每次都從記憶體讀取 再做判斷,程式終於與預期一致了。