2010-03-26 35 views
12

我明白關於常量的一個重大優惠是,您不必經過並更新那些遍佈整個地方的常量的代碼。這太好了,但假設你沒有明確地聲明它是一個常量。有什麼好處可以讓一個變量HAPPENS實際上不會被改變並使其不變,這會節省處理和/或代碼的大小等等嗎?常量的好處

基本上我有一個程序,編譯器說一個特定的變量沒有改變,因此可以被聲明爲一個常量,我只是想知道如果它增加了常量限定符會有什麼好處沒有什麼區別,那麼做出這樣的改變不會增加任何價值,因此不會浪費時間(同樣的情況發生在多個地方),然後「修復」所有這些變量。

+0

很多很好的答案,謝謝,我同意通過將它們聲明爲常量有一個好處就開發,但我想我在運行時的好處,不一定編譯時間...如果這樣做感。 – onaclov2000 2010-03-26 16:00:12

+0

運行時好處取決於程序編譯和運行的特定編譯器和機器;該語言描述了事情應該如何運作,而不是如何實施。 – jball 2010-03-26 16:10:52

+1

在編譯時檢測錯誤遠比在運行時可能實現的任何(最小)好處都重要得多。你可以通過購買更快的硬件來使代碼運行得更快,但是你不能以這種方式使代碼正確。如果代碼不正確,那麼運行它就沒有意義了。 – 2010-03-26 21:15:54

回答

17

如果你聲明一個變量是一個常量,那麼優化器通常可以通過'常量摺疊'來消除它,因此既加快了你的程序又節省了空間。作爲一個例子,考慮以下:

var int a = 5; 
const int b = 7; 
... 
c = process(a*b); 

編譯器將最終創建的指令由7乘以一個,並將其傳遞到「處理」,存儲在C的結果。但在這種情況下:

const int a = 5; 
const int b = 7; 
... 
c = process(a*b); 

編譯器只會傳遞35來處理,甚至不會編碼乘法。另外,如果編譯器知道進程沒有副作用(即簡單的計算),那麼它甚至不會調用進程。它只會將c設置爲進程(35)的返回值,爲您節省一個函數調用。

+0

好的答案,這些是那種我正在尋找的解決方案,例如.. – onaclov2000 2010-03-26 16:44:17

4

將變量標記爲常量會聲明您作爲程序員的意圖,即只要在代碼執行過程中訪問該變量,它就是一個一致的值。考慮它是一種文檔形式,也可以使編譯器在設計之前進行。

0

常量是在編譯時已知的不變的值,不會因程序的生命而改變。

常量與變量的區別在於一個重要方面,即一旦將某個值分配給一個常量,則不能隨後進行更改。

在運行時您可以確定常量中定義的值不會改變,因此程序不會中斷。

6

很多這取決於你的優化器有多好。

一個好的優化器會在編譯過程中用常量替換常量引用。這樣可以節省處理器週期,因爲生成的機器代碼使用立即值而不必從存儲器加載值。

一些優化器會認識到一個值在聲明後不會被修改,並將其轉換爲一個常量。不要依賴這種行爲。

此外,只要有可能,您的代碼應該強制執行在開發過程中所做的假設。如果一個「變量」永遠不會改變,那麼聲明它爲一個常量將有助於確保你自己或任何其他以後出現的開發者都不知不覺地修改「常量」。

3

正如您指出的那樣,您可能不會從將不變變量更改爲顯式常量中獲得任何直接益處。然而,在分析一個程序時,一個檢查就是看變量的聲明點到變量的參考點。因此,諸如在聲明之前被訪問的變量,或者在引用之前被設置和重置等的度量標準可以指示可能的錯誤。

通過顯式聲明常量爲常數,編譯器和分析工具知道你不打算在任何時候重置變量。

對於使用您的代碼的其他開發人員也是如此。他們可能會無意中設置了一個變量,而您可能沒有意識到這一點。聲明常量將避免這些類型的錯誤。

1

常量通常存儲在只讀存儲器中,這會阻止在執行期間更改它們,並且訪問它們可能會更快,或者至少與訪問RAM一樣快。

+0

我想這是依賴於這個特定的程序正在加載到的系統,我不確定這個,但看起來很有趣,我會在我身邊看看它。 – onaclov2000 2010-03-26 16:02:42

2

一個好處是,如果它是一個真正的恆定值,你不會意外地改變它。這個常數可以讓你稍後再糾正錯誤。作爲一個變量,任何人都可以在更新代碼時稍後改變它。

應該清楚哪些值永遠不會改變,哪些值可以改變。常量會強化你的意圖。從我的青春


現實世界的例子:

我們沒有一個內置的我是工作在一個系統上的PI值,有人創建了一個變量,名爲PI和沿途某處,某人(我)在我寫的程序中將該值修改爲3.14519(或其中的某些內容)...而不是pi的近似值3.14159)。它已經在代碼中的其他地方被設置,並且各種計算都從這一點開始。如果這個PI是一個常量(當然它後來改變了),我不會犯這個錯誤......至少不容易。

+0

我全心全意地接受你的陳述,我看到了這方面的好處,當我問這個問題時,我只是更多地考慮代碼的實際運行。 – onaclov2000 2010-03-26 16:07:18

19

如果您將某些內容聲明爲常量,然後意外嘗試修改其代碼中的值,那麼編譯器會告訴您有關您的錯誤。這種形式的靜態類型檢查實際上是使用常量的主要原因。

+0

**這是迄今爲止常量的最重要原因!**性能是很好的,但不是更多,也很大程度上取決於編譯器。但是當部分代碼假設某些東西總是具有相同的值,並且突然改變它可能會破壞執行!所以你決定什麼更重要的事實是,在非常特定的情況下,代碼運行速度最快或代碼執行正常;) – ntziolis 2011-03-31 11:38:10

+0

我想這個答案是遠離實踐。當你在實踐中編寫代碼並且在99%的情況下編譯器說'const不能被修改'時,這個事故不是一個意外的const修改,但你忘記了改變類型;) – abimelex 2016-07-22 07:25:49

2

除了已經說過的內容之外,將變量聲明爲常量給優化器帶來了更大的自由度。它可以消除讀取它的值,它可以消除創建臨時副本的需要,它可以消除變量本身(對於數字常量尤其如此)。

常量的另一個大的用例是常量對象。擁有一個常量對象(或者在函數中給出一個常量對象的引用),您可以確定您的對象沒有被修改,並且只允許調用該對象的方法(稱爲const方法)。然而,這對於C++來說是真實的,我不確定在Ada中同樣的概念是否有效。

2

另一種可能性,取決於語言,編譯器可能能夠在多線程環境中做一些優化,否則這些優化是不可能的。就像如果你說:

int b=x*f; 
int c=y*f; 

在多線程環境中,如果f是一個變量,編譯器可能有第二次操作之前產生f的重載。如果f是一個常數,並且編譯器知道它仍在寄存器中,則不需要重新加載。

3

編譯器可能會減少代碼大小。例如,在包裝System你會發現

type Bit_Order is (High_Order_First, Low_Order_First); 
Default_Bit_Order : constant Bit_Order := Low_Order_First; 

使之發出

case System.Default_Bit_Order is 
    when System.High_Order_First => 
     -- Big-endian processing 
    when System.Low_Order_First => 
     -- Little-endian processing 
end case; 

編譯器可以完全消除「錯誤」的分支,而你的代碼保持其便攜性(在x86機器上)。使用GNAT時,您需要使用非默認優化來實現此目的:-O2我認爲。

兩個分支必須是可編譯的 - 這是一個優化,而不是#ifdef處理。

2

當然,對於像C和Ada這樣的語言,編譯器會將常數中的值直接放入彙編指令中,這意味着不需要交換寄存器或從內存中讀取數據就不需要超過運行程序。這意味着兩件事:主要的是速度(可能在許多應用程序中並不那麼明顯,除非它們是嵌入式的),第二種是內存使用率(程序的最終二進制大小和運行時內存佔用量)。

此行爲將歸結爲語言和編譯器,因爲該語言將決定您(程序員)所做的任何假設,並因此限制語言的效率;和編譯器,因爲它的行爲可能會像處理任何其他變量一樣處理常量,以便預處理代碼並儘可能優化二進制速度和佔用空間。其次,正如itsmatt和jball所述,它使您能夠邏輯地將該項目視爲一個常量配置項目,而不是「變量」。特別是在高級編程語言和解釋型語言中。

2

這實際上並不是一個特定於Ada的問題。在變量常量

一般的作用:

  • 從「常量」偶然得到修改防止代碼中的錯誤。
  • 通知編譯器,它可以假定優化時該值不會改變。

阿達具體作用:

  • 對於數字常量,你不必指定類型(已命名的數字)。如果你用這種方式聲明它,它可以在任何表達式中使用這種類型的數字。如果使用變量,則只能用於該變量的確切類型的表達式中。

實施例:

Seconds_Per_Minute : constant := 60; 
Secs_Per_Min  : Integer := 60; 

type Seconds_Offset is 0 .. Integer'last; --' 

Min1, Secs1 : Integer; 
Min2, Secs2 : Seconds_Offset; 

... 
--// Using the named number, both of these compile no problem. 
Secs1 := Min1 * Seconds_Per_Minute; 
Secs2 := Min2 * Seconds_Per_Minute; 

--// The second line here gives an error, since Integer is a 
--// different type than Seconds_Offset. 
Secs1 := Min1 * Secs_Per_Min; 
Secs2 := Min2 * Secs_Per_Min; 
+0

Errm,因爲Positive是Integer的子類型,所以這裏沒有錯誤;我認爲你的意圖是分而不是繁衍! – 2010-03-27 11:39:27

+0

@Simon - 哇,很好的捕獲。我想我沒有編譯器錯誤的書面Ada代碼的條紋仍然如往常一樣,保持爲0.重新展示了我的意圖(我希望)。 – 2010-03-29 13:00:26

+0

嗯,我仍然在學習Ada,但是這非常有趣,你可以創建一個沒有類型的常量變量....我不知道! – onaclov2000 2010-08-27 03:35:02

2

還有另一個好處,堆棧和數據段的大小。

考慮:

function Recurser(i : Integer) return Integer is 
    ia : array(0..9) of Integer 
      := (1, 2, 3, 4, 5, 6, 7, 8, 9, 1000); 
    r : Integer; 
begin 
    if i = 0 then return 0; end if; 
    r := ia(i mod 10); 
    return r + Recurser(i - 1); 
end; 

每一次遞歸函數創建在堆棧中的320字節結構。但由於a的值不會改變,堆棧正在增加以保持一個恆定的變量。這對於小型堆棧的嵌入式平臺非常重要。

包級別變量還會增加數據段的大小。推動你的記憶需求。