幾乎每個嵌入式程序都需要處理一些不變的數據量;也就是在程序運行的時候這些量的值不會發生改變。舉個例子,我認為大多數人會覺得相當吃驚,當調用:
printf("Hello");
后輸出的不是Hello字符串。很明顯,像"Hello"這樣的字符串應該是不變的量。除了文字常量,許多程序還需要配置數據,狀態轉換表,或者常數系數,這些都應該是不變的。我們通常將不變的量稱為只讀的,與之相反的是可以讀寫的變量。
在許多桌面應用中,只是邏輯上而不是物理上區分只讀和讀寫。鏈接器可能將所有的只讀數據放在一個數據段中來給促進程序加載到內存中去。
在許多嵌入式系統中,只讀和讀寫不僅僅只是邏輯上的區別了。大多數嵌入式系統程序不像桌面應用程序那樣從磁盤中加載數據。嵌入式系統中,只讀數據將永久保存在ROM中。很明顯,讀寫數據不能放在ROM中;它們必須放在RAM里面。因此,編譯器必須要能夠區分讀寫數據和只讀數據,這樣它才有辦法將只讀數據放入ROM中,將讀寫數據放入RAM里。
事實上,就ROM和RAM而言,在嵌入式系統中都是很缺乏的。例如,一些系統在生產線上讓所有的model使用同一個控制程序,并且為每個根據每個model的需求來配置相應的數據。這些系統將二進制代碼和只讀數據分別放在不同的存儲段中,以便將數據放在ROM中和代碼區分開來。因此,雖然每個model使用不同的ROM來存儲配置數據,但是在生產線上每個model都可以使用相同ROM來存儲二進制代碼。
典型的C/C++嵌入式系統編譯器根據這些需求,它們將代碼和數據映射到以下幾個不同的邏輯段:
O 代碼(Code)段(也叫<i>text</i>段):只讀段,包含程序代碼
O 文字(Literal)段:只讀段,包含初始化數據
O 初始化數據(也叫<i>plain data</i>):讀寫段,包含用于程序啟動的初始化數據
O 未初始化數據(也叫<i>bss</i>):讀寫段,包含一些未初始化的數據,直到程序使用到。
編譯器和鏈接器也可以提供一種像#pragma一樣的開關或者命令使得你可以將一些邏輯段放入一個物理段中。例如,你可以將文字常量和代碼固化到ROM中,也可以和初始化的數據一起放入RAM。在這個段模型下,將文字常量放到ROM中是相當容易的。編譯器會將所有的文字常量收集到文字常量段中。鏈接器和其他的后端工具會確保將文字常量段放入ROM中。
將非文字常量放入到ROM中是一個比較復雜點的問題。編譯器必需將初始化的只讀數據和初始化的讀寫數據區分開來。很明顯,未初始化的數據不能是只讀的,如果這樣的化那么就是沒用的量,所以它必需放在RAM中。然而,區分初始化的數據的方法并不是很明顯。
例如,當一個編譯器遇到如下聲明:
unsigned char two_to_the[]
= { 1, 2, 4, 8, 16, 32, 128, 256 };
編譯器是將其放入到文字常量段,還是初始化數據段中呢?
即使two_to_the是放在RAM中的讀寫數據,初始化數據的復制本可能出現在ROM中。在程序啟動的時候將初始化的值從ROM中復制到RAM。
大多數嵌入式開發工具支持將初始化的數據放到ROM中,當編譯一個源文件的時候,編譯器將所有初始化的數據放到一個段中。默認情況下這個段就是初始化數據段。然而,你可以使用一個編譯器開關告訴編譯器將初始化的數據復制到文字常量段中。因此,你可以將所有需要設置為只讀的數據集中到一個源文件中并使用相應的開關來將他們放入文字常量段。這個方法有一個要注意的就是你需要根據你物理上的需求來整理某些部分,而不是邏輯上的。由此你可能會想將一些只讀量和讀寫數據放在使用它們的同一個源文件中去,但是不能這樣干。你必需將只讀數據放在一個單獨的源文件中,程序需要做出更多的努力來讀取它們 [1]。
許多編譯器提供pragmas這樣的開關。它可以在一個相同文件中將數據放到一個不同的段中。舉個例子:
#pragma data(“literal”)
unsigned char two_to_the[]
= { 1, 2, 4, 8, 16, 32, 64, 128 };
#pragma data()
第一個pragma告訴編譯器將以下的定義放入到文字常量段中去。第二個pragma告訴編譯器返回到之前的段中去。
不幸的是,編譯器們在pragma語法上都不太一樣,有時顯得很戲劇性。C和C++標準都說明了pragma語法的存在性,但是沒有強制編譯器們要支持某種特定的pragma語法。以此,使用到pragma的代碼移植性很差。
以上的方法都有一個問題,就是沒有提供一種防止對只讀變量寫操作的方法。一個編譯器應該為以下這種操作報錯:
two_to_the[i] = 0;
但是不幸的是卻不可以。你可能都不會注意到這個錯誤直到你運行程序。
接下來進入const修飾符。const修飾符可以使得編譯器檢測到對只讀數據的寫操作。例如:
const unsigned char two_to_the[]
= { 1, 2, 4, 8, 16, 32, 64, 128 };
將two_to_the定義為“包含只讀數據的字符型數組”。const是two_to_the類型符的一部分,編譯器用它來核實以后對two_to_the的操作的合法性。舉個例子,在上面的聲明后,下面這樣一個復制語句:
two_to_the[i] = 0;
是一個編譯錯誤。
僅僅使用const修飾符是不足以將其放入到ROM中去的。你同樣需要使用鏈接器和后端工具將其放入到你想放的位置中去。const修飾符只是提供了一個開始而已。
將數據放到ROM僅僅只是const修飾符幾個用法中的一個。還有,盡管使用了后端工具相應的支持,在聲明的時候時候const修飾符也不一定能確保將數據放到ROM中去。因為聲明必需滿足其他的語法限制。在未來的幾個月,我將細說這些限制并且講述const的其他用法。<small>[ESP]</small>
本文是翻譯自: Dan Saks 的 Placing Data into ROM