我們知道,數據分為兩種,一種為只讀,一種為可讀可寫,為了防止一些不變的數據被程序意外的修改,有必要對它進行保護。這就是 const 的作用。在單片機中,不變的數據(比如代碼,比如用戶一些固定不變的數據)一般存放在 FLASH 中,而 FLASH 在一般情況下是只讀的(事實上可以通過操作修改 FLASH,這種方式稱之為 IAP ,即你可以不斷更新程序的原因),而一些可變的數據一般是放在 RAM 中的,這些數據可能被指針意外的修改。所以就需要通過一種方式來將這些數據存放在 FLASH 中以防意外修改,而最好的保護就是硬體水平上的支持,這就是 const 的作用,它可以將你的數據放在 FLASH 中,但它的作用不只是如此。
const: 限定一個變量不允許改變,產生靜態作用,const 在一定程度上可以提高程序的安全性和可靠性。
const 推出的初始目的,正是為了取代預編譯指令,消除它的缺點,同時繼承它的優點。
聲明為 const 的變量是不能被用戶改變的(意思就是說你不允許你通過代碼去修改這個值),因為編譯器會將該變量放在只讀區,比如在 KEIL 開發平臺下,聲明為 const 的變量放在 FLASH 區,這樣即使你使用取地址符 & 獲取聲明為 const 變量地址,並通過指針進行修改,雖然編譯器不報錯,但也是無法進行修改的,因為 STM32 進行 FLASH 編程是有條件的。
你會發現雖然 p 獲取了 N 的地址,但因為 N 存放在 FLAH(0x0800 0000 開始地址是 FLASH)中,所以即使通過指針的方式也是無法間接改變N的值的。
可以看到運行 *p = 4 的代碼後,N 並沒有發生改變。但是編譯器確實也沒報錯。但是如果你直接使用 N = 4; 的話,肯定是報錯的,因為你的N已經被申明為 const 了。
《C語言深度剖析》中關於 const 的介紹發現和 KEIL 情況不一樣。
在 KEIL 中進行相關代碼的編寫,編譯,最後可以看到如下結果:
這是仿真模式下兩個地址的內容,一個存放在 FLASH,一個存放在 RAM 中,並且當修改 FLAH 的內容之後(因為是軟體仿真模式,可以直接修改值),復位重新運行,你會發現 RAM 的內容對應改變了(重新運行後,進入 main 函數之前,有一段拷貝代碼,就是函數外申明的一些變量的初始化過程),這就說明,在 STM32、KEIL 環境下,並不是《C語言深度剖析》中說的只有一份內存,而是每一個都有一個,申明為 const 情況跟使用宏定義的方式是一樣的。
以下是 Watch 中的內容:
但其實上面的結論是在使用 & 將 N 的地址獲取後的結果(從上圖可以看到 p 的值),實際上代碼中如果沒有獲取 N 的地址時,情況又不一樣了。
內存情況:
在刪去獲取 N 地址後的內存情況,可以發現 N 的值為 0x2000470,和 FLAH 地址 0x08000000 一樣。
這像一個地址。但通過 Memory 查看這個地址發現存放的不是 5。
根據 ARM 內核的知識可以知道,0x08000000 地址存放的其實是棧頂指針,也就是說 N 存放的是棧頂指針嗎?顯然不是。
然後對 .map 地址映射文件進行搜索,你會發現,根本沒有 N 的地址。這樣說來,N 在內存的位置對用戶是不可見的,而是由編譯器自動處理了。
那麼有沒有辦法找到這個拷貝源頭呢。之前我說過,先前能找到拷貝的源頭純屬偶然,有沒有什麼方法可以找到呢?這其中的難點就是進入 main 函數之前的那段拷貝代碼不是我們用戶自己寫的,而是 C 編譯器自動處理的,怎麼辦?
這個時候就需要請出一個關鍵人物:數據觀察點(關於數據觀察點,將有專門的一小節詳細說明,感興趣的可以關注我)。
我們知道,不管如何,因為 FLASH 存放著變量初始值,然後在程序運行的時候才將 FLASH中的值初始化到 RAM 中去,也就是我們使用的 RAM 變量,那麼必然存在通過總線進行數據傳輸的過程,所以可以通過數據觀察點的功能實現對地址的監控,雖然我們不知道 FLASH 的地址,但是我們知道 RAM 的地址,所以只要對變量i進行監控,就可以通過內核的寄存器找到 FLASH 地址了。如下:
這裡的 0x20000000 就是 i 的 RAM 地址,最終可以找到 FLASH 的位置:
由此可以知道,FLAH 中也是有多個相同副本存在的。
因此可以得出結論,在 STM32、KEIL 的環境下,《C語言深度剖析》對於兩者的說法在這裡不適用。
看了那麼多,沒有足夠的基礎是很難知道我在講什麼,下面用一張圖進行說明,希望可以解答你的疑惑。
希望你在看完這張圖之後,再回過頭看看前面的那些話,對你應該會有幫助的。
喜歡本系列 C 語言文章的可以關注我哦!