Ada 樣式指南/可移植性
關於可移植性的討論通常集中在計算機系統之間的差異,但開發和執行時環境也可能發生變化。
- 可移植性(軟體)
- 軟體從一個計算機系統或環境轉移到另一個計算機系統或環境的難易程度(IEEE 詞典 1984)。
大多數可移植性問題並非純粹的語言問題。可移植性涉及硬體(位元組序、裝置 I/O)和軟體(實用程式庫、作業系統、執行時庫)。本章不會討論這些具有挑戰性的設計問題。
本章確實確定了從一個平臺或編譯器遷移到另一個平臺時,Ada 特定的更常見可移植性問題。它還建議了隔離不可移植程式碼的方法。透過使用 Ada 的實現隱藏功能,可以顯著降低移植成本。
事實上,許多語言可移植性問題透過 Ada 語言本身的嚴格定義得到了解決。在大多數程式語言中,不同的方言盛行,因為供應商出於各種原因擴充套件或稀釋語言:符合程式設計環境或針對特定應用領域的特性。Ada 編譯器驗證能力 (ACVC) 由美國國防部在 Ada 驗證機構 ASD/SIDL(萊特-帕特森空軍基地)開發,以確保實現者嚴格遵守 Ada 標準。
作為 Ada 嚴格定義的一部分,某些結構被定義為錯誤,執行錯誤結構的效果是不可預測的。因此,錯誤結構顯然不可移植。錯誤結構和有界錯誤在指南 5.9.10 中討論,本章不再重複。
大多數剛接觸這種語言的程式設計師希望 Ada 消除所有可移植性問題;它絕對沒有。Ada 的某些領域尚未涵蓋驗證。Ada 的定義將某些細節留給實現者。編譯器實現者在這些細節方面做出的選擇會影響可移植性。
1995 年標準中批准的 Ada 語言修訂版產生了新的可移植性問題領域。有些程式旨在具有較長的生命週期,可能從 Ada 83(Ada 參考手冊 1983)開始,但過渡到 Ada 95(Ada 參考手冊 1995)。雖然本樣式指南側重於當前的 Ada 標準,並沒有解決過渡問題,但使用語言的某些特性會存在可移植性問題。這些問題圍繞著在 Ada 參考手冊(1995)的附錄 J 中指定為過時的語言特性。
語言的結構是為了滿足一系列需求而開發的。即使這些結構可能影響可移植性,也可以合法地使用它們。本章中的許多指南都體現了一些提高可移植性的通用原則。他們是
- 識別可能對相關實現或平臺上的可移植性產生不利影響的 Ada 結構。
- 依賴那些依賴所有相關實現共享的特徵的 Ada 結構。避免使用那些實現特徵在相關平臺上不同的結構。
- 如果必須使用程式的不可移植特性,請對其進行本地化和封裝。
- 突出顯示可能導致可移植性問題的結構的使用。
這些指南不能漫不經心地應用。其中許多涉及對 Ada 模型及其實現的詳細瞭解。在許多情況下,您將不得不權衡效率和可移植性之間的利弊。閱讀本章應該會提高您對所涉及的權衡的認識。本章中的內容主要來自三個來源:Ada 執行時環境工作組 (ARTEWG) 的 Ada 執行時實現依賴項目錄 (ARTEWG 1986);Nissen 和 Wallis 關於 Ada 中的可移植性和樣式的書 (Nissen 和 Wallis 1984);以及 SofTech 為美國空軍撰寫的一篇關於 Ada 可移植性指南的論文 (Pappas 1985)。最後一種來源 (Pappas 1985) 包含了其他兩種來源,並對問題進行了深入解釋,提供了大量示例和最小化可移植性問題的技術。Conti (1987) 是瞭解 Ada 實現者允許的自由度和經常用於做出決定的標準的寶貴參考。
本章的目的是以本書的指南格式提供可移植性問題的摘要。本章並未包含參考文獻中標識的所有問題,而僅包含最重要的問題。有關深入介紹,請參見 Pappas (1985)。本章還介紹了一些額外的指南,並在適用的情況下對其他指南進行了詳細說明。有關 Ada I/O 可移植性問題的進一步閱讀,請參見 Matthews (1987)、Griest (1989) 和 CECOM (1989)。
本章中的一些指南交叉引用並對本書中的其他指南施加了更嚴格的限制。這些限制適用於強調可移植性時。
本章中的指南經常用“考慮……”措辭,因為硬性規定不能適用於所有情況。您在特定情況下做出的具體選擇涉及設計權衡。這些指南的基本原理旨在讓您洞悉其中的一些權衡。
本節介紹了一些編寫可移植 Ada 程式的通用原則。它包括關於您應該對許多 Ada 特性及其實現做出的假設的指南,以及關於使用其他 Ada 特性以確保最大可移植性的指南。
- 在旨在具有較長生命週期的程式或元件中,避免使用 Ada 參考手冊(1995)的附錄 J 宣告為“過時”的 Ada 特性,除非需要使用該特性才能向後相容 Ada 83(Ada 參考手冊 1983)。
- 記錄任何過時特性的使用情況。
- 避免使用以下特性
- 預定義環境中包的簡短重新命名(例如,Text_IO 而不是 Ada.Text_IO)
- 對 ! 替代為 |, : 替代為 #,以及 % 替代為引號的字元替換
- 浮點型別的精度降低的子型別
- 應用於私有型別的'Constrained 屬性
- 預定義的包 ASCII
- 異常 Numeric_Error
- 各種表示規範,包括 at 子句、mod 子句、中斷入口和 Storage_Size 屬性
十年對 Ada 83 使用情況的反思得出結論,原始語言中的一些特性不像最初預期的那樣有用。這些特性在 Ada 95 修訂版中被其他特性所取代。理想情況下,應該完全刪除過時的特性,但這會阻止程式從 Ada 83 向 Ada 95 的向上相容過渡。因此,過時的特性仍然存在於語言中,並在 Ada 參考手冊(1995)的附錄 J 中明確標註為過時。附錄 J 中列出的特性是語言在下次修訂時刪除的候選特性。如果程式的壽命可能超出下次語言修訂版,則應避免使用過時的語言特性,除非與 Ada 83 的向後相容性強制使用它們。
當您例項化 Ada.Text_IO.Float_IO 時,Default_Fore 和 Default_Aft 欄位的值將從用於例項化的實際浮點型別的'Fore 和'Aft 屬性的值設定。如果您宣告一個精度降低的浮點型別,然後使用它來例項化 Ada.Text_IO.Float_IO,則輸出欄位寬度將根據精度降低的型別確定,儘管實現精度保持不變(基本原理 1995,§3.3)。
- 對以下內容在潛在目標平臺上提供的支援做出明智的假設
- 整數型別可用位數(範圍約束)
- 浮點型別可用的精度小數位數
- 定點型別可用位數(增量和範圍約束)
- 源文字每行字元數
- Root_Integer 表示式位數
- Duration 範圍內的秒數
- Duration'Small 的毫秒數
- 十進位制型別的最小和最大比例
- 避免對 Character 型別中包含的值和值的數量做出假設。
- 這些是專案或應用程式可能假設的實現提供的最小值(或 Duration'Small 的最小精度)。不能保證給定的實現提供超過最小值的數目,因此專案或應用程式也會將這些視為最大值。
- 整數型別可用 16 位(-2**15 .. 2**15 - 1)
- 浮點型別可用的精度為 6 位小數
- 定點型別可用 24 位
- 源文字每行 200 個字元
- 表示式為 16 位
- -86_400 .. 86_400 秒(1 天)用於 Duration 範圍(如 Ada 參考手冊 1995,第 9.6 節 [註釋] 中所述)
- Duration'Small 為 20 毫秒(如 Ada 參考手冊 1995,第 9.6 節 [註釋] 中所述)
必須對某些實現特定的值做出一些假設。假設的精確值應該涵蓋大多數目標裝置。選擇值的最低公分母可以提高可移植性。實現可能會提供特定於區域設定或環境的備用字元集。例如,IBM 相容 PC 上的實現可能支援該機器的本機字元集而不是 Latin 1。因此,某些字元值可能支援也可能不支援,例如,笑臉。
目前可用於嵌入式系統中的微型計算機中,16 位和 32 位處理器很常見。使用當前的表示方案,6 位小數的浮點精度意味著表示尾數至少 21 位寬,在 32 位表示中為指數和符號留下 11 位。這與目前可用於嵌入式系統市場的浮點硬體的資料寬度相對應。定點數字的 32 位最小值與浮點數字的精度和儲存要求相對應。Root_Integer 表示式的 16 位示例與 Integer 儲存相匹配。(如果應用程式僅針對具有相應 32 位作業系統和支援編譯器的 32 位處理器,則可以假設 32 位整數。)
預定義型別 Duration 的值範圍和精度的值是在 Ada 參考手冊 1995,第 9.6 節 [註釋] 中表達的限制。你不應該期望實現提供更寬的範圍或更細的粒度。
在大多數情況下,可以假設標準模式 Ada 字元集 Latin 1 用於型別 Character 和包 Character.Latin_1、Character.Handling 和 Strings.Maps 的內容和內部行為。但是,這並不意味著目標硬體平臺能夠顯示整個字元集。除非有意生成具有特定目的的不可移植使用者介面,否則你不應該使用非標準 Ada 字元集。
- 對每個包、子程式和任務使用突出顯示的註釋,其中存在任何不可移植的特性。
- 對於每個使用的不可移植特性,描述對該特性的期望。
------------------------------------------------------------------------
package Memory_Mapped_IO is
-- WARNING - This package is implementation specific.
-- It uses absolute memory addresses to interface with the I/O
-- system. It assumes a particular printer's line length.
-- Change memory mapping and printer details when porting.
Printer_Line_Length : constant := 132;
type Data is array (1 .. Printer_Line_Length) of Character;
procedure Write_Line (Line : in Data);
end Memory_Mapped_IO;
------------------------------------------------------------------------
with System;
with System.Storage_Elements;
package body Memory_Mapped_IO is
-- WARNING: Implementation specific memory address
Buffer_Address : constant System.Address
:= System.Storage_Elements.To_Address(16#200#);
---------------------------------------------------------------------
procedure Write_Line (Line : in Data) is
Buffer : Data;
for Buffer'Address use Buffer_Address;
begin -- Write_Line
-- perform output operation through specific memory locations.
...
end Write_Line;
---------------------------------------------------------------------
end Memory_Mapped_IO;
------------------------------------------------------------------------
明確地註釋每個可移植性違規將提高其可見性並幫助移植過程。對不可移植特性的期望的描述涵蓋了原始實現的供應商文件不可用於執行移植過程的人員的常見情況。
- 考慮僅使用無引數過程作為主子程式。
- 考慮使用 Ada.Command_Line 訪問環境中的值,但要認識到該包的行為甚至其規範是不可移植的(參見指南 7.1.6)。
- 封裝和記錄所有對包 Ada.Command_Line 的使用。
以下示例封裝了從環境傳遞的假設“執行模式”引數的引數。它封裝了引數的預期位置和預期值,並在環境無法提供資訊的情況下提供預設值
package Environment is
type Execution_Mode is (Unspecified, Interactive, Batch);
function Execution_Argument return Execution_Mode;
...
end Environment;
----------------------------------------------------------------------
with Ada.Command_Line; use Ada.Command_Line;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
package body Environment is
function Execution_Argument return Execution_Mode is
Execution_Argument_Number : constant := 1;
Interactive_Mode_String : constant String := "-i";
Batch_Mode_String : constant String := "-b";
begin
if Argument_Count < Execution_Argument_Number then
return Unspecified;
elsif To_Unbounded_String (Argument (Execution_Argument_Number)) =
Interactive_Mode_String then
return Interactive;
elsif To_Unbounded_String (Argument (Execution_Argument_Number)) =
Batch_Mode_String then
return Batch;
else
return Unspecified;
end if;
end Execution_Argument;
end Environment;
預定義的語言環境宣告包 Ada.Command_Line,它提供了一種標準化的方式,使程式能夠獲取命令列的值。因為所有 Ada 編譯器都必須實現預定義語言環境中的包,所以你可以使用該包建立一個更可移植、可維護和可讀的程式。但是,你應該意識到,儘管語言定義了該包的物件和型別配置檔案,但它並沒有強制函式結果與任何其他實體或操作之間存在關係,因此,允許可能存在不可移植的行為和規範。
函式 Ada.Command_Line.Argument_Count 返回的值是實現相關的。不同的作業系統對命令列引數的解析和含義遵循不同的約定。為了提高程式的可移植性,假設最簡單的情況:外部執行環境不支援向程式傳遞引數。
一些作業系統能夠從函式中獲取和解釋接近 0 的返回整數值,但許多其他作業系統則不能。此外,許多即時嵌入式系統並非設計為終止,因此具有 out 或 in out 模式引數的函式或過程將不適用於此類應用程式。
這留下了具有 in 引數的過程。雖然一些作業系統能夠在程式啟動時將引數傳遞給程式,但其他作業系統則不能。此外,即使周圍環境能夠提供引數,實現也可能無法對這些引數執行型別檢查。
即時嵌入式應用程式可能沒有提供引數的“操作員”啟動程式,在這種情況下,更適合讓程式與包含適當常數值的包一起編譯,或者讓程式從開關設定或下載的輔助檔案中讀取必要的值。無論哪種情況,周圍啟動環境的變化都太大,無法依賴於由(子程式)引數向主子程式傳遞的最後時刻(程式)引數化所暗示的那種引數化。POSIX 5 提供了一個標準的作業系統命令列介面,該介面可能是 Ada 命令列工具的更合適的替代方案,具體取決於應用程式的實現系列。
- 建立專門設計的包,以隔離硬體和實現依賴,並且這些包的規範在移植時不會發生變化。
- 如果硬體或實現相關的程式碼是出於機器或解決方案效率的原因,則要明確說明目標。
- 對於隱藏實現依賴關係的包,為不同的目標環境維護不同的包體。
- 將中斷接收任務隔離到實現相關的包中。
- 有關實現相關的功能列表,請參見 Ada 參考手冊(1995 年)的附件 M。
例子
[edit | edit source]參見指南 7.1.3。
基本原理
[edit | edit source]將硬體和實現依賴項封裝在包中,允許程式碼的其餘部分忽略它們,從而實現完全可移植性。它還將依賴項本地化,從而清楚地表明哪些程式碼部分在移植程式時可能需要更改。
一些實現相關的功能可用於實現特定的效能或效率目標。對這些目標進行註釋可確保程式設計師在移植到不同的實現時能夠找到實現這些目標的合適方法,或明確地認識到它們無法實現。
中斷入口是實現相關的功能,可能不受支援(例如,VAX Ada 使用編譯指示將系統陷阱分配給“正常”的 rendezvous)。但是,在大多數嵌入式即時系統中無法避免中斷入口,因此可以合理地假設 Ada 實現支援它們。中斷的值由實現定義。將其隔離。
註釋
[edit | edit source]您可以使用 Ada 編寫依賴於機器的程式,這些程式以與 Ada 模型一致的方式利用實現,但會在 Ada 允許實現自由的地方做出特定的選擇。這些機器依賴項應與程式碼的任何其他實現相關功能以相同的方式進行處理。
實現新增的功能
[edit | edit source]指南
[edit | edit source]- 避免使用供應商提供的包。
- 避免使用新增到預定義包中的功能,這些功能未在 Ada 語言定義或專門需求附件中指定。
基本原理
[edit | edit source]供應商新增的功能不太可能由其他實現提供。即使大多數供應商最終提供類似的附加功能,它們也不太可能具有相同的公式。實際上,不同的供應商可能使用相同的公式來表示(語義上)完全不同的功能。有關供應商提供的異常的更多資訊,請參見指南 7.5.2。
Ada 引入了許多在 Ada 83(Ada 參考手冊 1983 年)中不存在的新編譯指示和屬性。這些新的編譯指示和屬性可能會與實現定義的編譯指示和屬性衝突。
異常
[edit | edit source]有許多型別的應用程式需要使用這些功能。例如,在供應商檔案系統上進行標準化的多語言系統、與供應商產品緊密整合的應用程式(即使用者介面)以及出於效能原因的嵌入式系統。將這些功能的使用隔離到包中。
如果供應商提供的包以可編譯的原始碼形式提供,則使用該包不會使程式不可移植,前提是該包不包含任何不可移植的程式碼並且可以合法地包含在您的程式中。
專門需求附件
[edit | edit source]指南
[edit | edit source]- 使用專門需求附件中定義的功能,而不是供應商定義的功能。
- 清楚地記錄對來自專門需求附件的任何功能的使用(系統程式設計、即時系統、分散式系統、資訊系統、數值和安全與安全性)。
基本原理
[edit | edit source]專門需求附件定義了特定應用領域的標準,而不會擴充套件語言的語法。如果您依賴於附件中標準化的功能,而不是依賴於特定的供應商擴充套件,則可以更輕鬆地在供應商實現之間移植具有特定領域需求(例如,分散式系統、資訊系統)的程式。附件的目的是為幾個預計使用 Ada 的應用領域中面臨的問題提供一致且統一的解決方法。由於不同的編譯器會支援不同的附件集(如果有),因此如果您依賴於任何給定附件中定義的功能,您可能會遇到可移植性問題。
專門需求附件提供了超出核心語言定義的功能。由於編譯器不需要支援專用附件,因此您應該儘可能地將這些功能的使用本地化。透過記錄其用法,您將為未來的程式設計師留下潛在移植困難的記錄。
對引數傳遞機制的依賴
[edit | edit source]指南
[edit | edit source]- 不要編寫其正確執行依賴於實現使用的特定引數傳遞機制的程式碼(Ada 參考手冊 1995 年,§6.2 [Annotated];Cohen 1986)。
- 如果子程式具有多個給定子型別的形式引數,其中至少一個為 [in] out,則確保子程式能夠正確處理兩個形式引數都表示同一實際物件的情況。
例子
[edit | edit source]此程式的輸出取決於所使用的特定引數傳遞機制
------------------------------------------------------------------------
with Ada.Integer_Text_IO;
procedure Outer is
type Coordinates is
record
X : Integer := 0;
Y : Integer := 0;
end record;
Outer_Point : Coordinates;
---------------------------------------------------------------------
procedure Inner (Inner_Point : in out Coordinates) is
begin
Inner_Point.X := 5;
-- The following line causes the output of the program to
-- depend on the parameter passing mechanism.
Ada.Integer_Text_IO.Put(Outer_Point.X);
end Inner;
---------------------------------------------------------------------
begin -- Outer
Ada.Integer_Text_IO.Put(Outer_Point.X);
Inner(Outer_Point);
Ada.Integer_Text_IO.Put(Outer_Point.X);
end Outer;
------------------------------------------------------------------------
如果引數傳遞機制是透過複製,則標準輸出檔案上的結果為
0 0 5
如果引數傳遞機制是透過引用,則結果為
0 5 5
以下程式碼片段顯示了在用表示同一物件的實際引數呼叫過程時存在潛在的有限錯誤的情況
procedure Test_Bounded_Error (Parm_1 : in out Integer;
Parm_2 : in out Integer) is
procedure Inner (Parm : in out Integer) is
begin
Parm := Parm * 10;
end Inner;
begin
Parm_2 := 5;
Inner (Parm_1);
end Test_Bounded_Error;
在執行過程 Test_Bounded_Error 時,Parm_1 和 Parm_2 都表示物件 Actual_Parm。執行第一個語句後,物件 Actual_Parm 的值為 5。當呼叫過程 Inner 時,其形式引數 Parm 表示 Actual_Parm。無法確定它表示 Parm_1 的舊值(在本例中為 1)還是新值(在本例中為 5)。
Actual_Parm : Integer := 1;
...
Test_Bounded_Error (Actual_Parm, Actual_Parm); -- potential bounded error
基本原理
[edit | edit source]某些複合型別(未標記的記錄和陣列)可以透過複製或引用傳遞。如果有兩個或多個相同型別的形式引數,其中一個或多個是可寫的,那麼您應該記錄是否假定這些形式引數不表示相同的實際物件。同樣,如果具有給定子型別的形式引數的子程式也向上級引用了此相同型別的物件,那麼您應該記錄是否假定形式引數表示與向上級引用中命名的物件不同的物件。在這些物件可以透過不同的形式引數路徑訪問的情況下,可能會引發 Program_Error 異常,可能會讀取新值,或者可能會使用物件的舊值(Ada 參考手冊 1995 年,§6.2 [Annotated])。
另請參見指南 8.2.7。
異常
[edit | edit source]通常,在將 Ada 與外部程式碼進行介面時,無法避免對特定實現使用的引數傳遞機制的依賴。在這種情況下,將對外部程式碼的呼叫隔離在一個介面包中,該包匯出不依賴於引數傳遞機制的操作。
- 避免依賴Ada中某些結構的評估順序。
該程式的輸出取決於子程式引數的評估順序,但是Ada參考手冊1995,第6.4節[註釋]指定這些評估以任意順序執行。
package Utilities is
function Unique_ID return Integer;
end Utilities;
package body Utilities is
ID : Integer := 0;
function Unique_ID return Integer is
begin
ID := ID + 1;
return ID;
end Unique_ID;
end Utilities;
--------------------------------------------------------------------------------
with Ada.Text_IO;
with Utilities; use Utilities;
procedure P is
begin
Ada.Text_IO.Put_Line (Integer'Image(Unique_ID) & Integer'Image(Unique_ID));
end P;
如果“&”函式的引數按文字順序評估,則輸出為
1 2
如果引數以相反順序評估,則輸出為
2 1
Ada語言將某些評估定義為以任意順序發生(例如,子程式引數)。儘管依賴評估順序可能不會對特定實現上的程式產生不利影響,但程式碼在移植時可能無法正確執行。例如,如果子程式呼叫的兩個實際引數具有副作用,則程式的效果可能取決於評估順序(Ada參考手冊1995,第1.1.4節[註釋])。避免任意順序依賴,但也認識到,即使這種無意錯誤也可能禁止可移植性。
Ada與數值計算相關的功能的設計非常謹慎,以確保該語言可以在嵌入式系統和數學應用程式中使用,在這些應用程式中精度很重要。這些功能儘可能地實現了可移植性。但是,在最大程度地利用特定機器上數值計算的可用精度與最大程度地提高Ada數值結構的可移植性之間不可避免地存在權衡。這意味著,如果要保證生成的程式的完全可移植性,則必須非常謹慎地使用這些Ada功能,尤其是數值型別和表示式。
- 避免在Standard包中使用預定義的數值型別。使用範圍和位數宣告,並讓實現選擇合適的表示形式。
- 對於需要比全域性假設提供的精度更高的程式,請定義一個包,該包宣告私有型別和操作,具體需求見Pappas(1985),其中有完整解釋和示例。
- 考慮對以下內容使用預定義的數值型別(Integer,Natural,Positive):
- 陣列的索引,其中索引型別並不重要,例如String型別
- “純”數字,即沒有關聯物理單位的數字(例如,指數)
- 用於控制重複或迭代計數的值
下面的第二個和第三個示例無法在字長為16位的機器上表示為Integer的子範圍。下面的第一個示例允許編譯器在必要時選擇多字表示形式。
使用
type Second_Of_Day is range 0 .. 86_400;
而不是
type Second_Of_Day is new Integer range 1 .. 86_400;
或
subtype Second_Of_Day is Integer range 1 .. 86_400;
實現人員可以自由地定義預定義數值型別的範圍。將程式碼從精度更高的實現移植到精度較低的實現是一個耗時且容易出錯的過程。許多錯誤只有在執行時才會報告。
這不僅適用於數值計算。如果您忽略了為整數離散範圍(陣列大小,迴圈範圍等)使用顯式宣告的型別,那麼就會遇到一個很容易被忽略的問題(請參見指南5.5.1和5.5.2)。如果您在指定索引約束和其他離散範圍時沒有提供顯式型別,則會假設預定義的整數型別。
當您明智地使用預定義的數值型別時,它們很有用。您不應使用它們來避免宣告數值型別,否則您將失去強型別的優勢。當您的應用程式處理不同型別的數量和單位時,您應該透過使用不同的數值型別來將它們分開。但是,如果您只是在迭代逼近演算法中計算迭代次數,那麼宣告特殊的整數型別可能就過分了。預定義的求冪運算子**要求其右運算元的型別為整數。
您應該使用預定義的型別Natural和Positive來操作預定義語言環境中某些型別的值。String和Wide_String型別使用Positive型別的索引。如果您的程式碼使用不相容的整數型別索引字串,您將被迫進行型別轉換,從而降低其可讀性。如果您正在執行切片和串聯等操作,那麼您的數值陣列索引的子型別可能無關緊要,您最好使用預定義的子型別。另一方面,如果您的陣列表示一個表(例如,雜湊表),那麼您的索引子型別就很重要,您應該宣告一個不同的索引型別。
該指南允許使用另一種方法。如指南7.1.5所建議,實現依賴關係可以封裝在為此目的而設計的包中。這可能包括定義一個32位整數型別。然後,可以從該32位型別派生其他型別。
- 當效能和精度是主要問題時,請使用支援數值附錄(Ada參考手冊1995,附錄G)的實現。
數值附錄定義了浮點和定點算術的精度和效能要求。該附錄提供了一種“嚴格”模式,在該模式下,編譯器必須支援這些要求。為了保證您的程式的數值效能可移植,您應該在嚴格模式下進行編譯和連結。如果您的程式依賴於嚴格模式的數值屬性,那麼它只能移植到也支援嚴格數值模式的其他環境中。
浮點數的精度基於在儲存器中可以精確表示的機器數。當暫存器包含的位數多於儲存器時,暫存器中的計算結果可能介於兩個機器數之間。可以使用'Pred和'Succ屬性逐步遍歷機器數。其他屬性返回尾數、指數、基數和其他特徵的值,這些特徵屬於浮點數和定點數。
- 仔細分析您真正需要的精度。
浮點計算使用等效於實現的預定義浮點型別執行。內部計算中額外的“保護”位有時會降低Ada宣告中必須指定的位數。這在程式要執行的實現之間可能不一致。它也可能導致錯誤地得出結論,即宣告的型別足以滿足所需的精度。
您應該選擇數值型別宣告以滿足最低精度(最小位數),以提供所需的精度。需要仔細分析才能證明宣告足夠。當您遷移到精度較低的機器時,您可能可以使用相同的型別宣告。
- 不要逼近機器的精度極限。
基本原理
[edit | edit source]僅僅因為兩臺不同的機器在浮點數尾數中使用相同數量的數字並不意味著它們將具有相同的算術屬性。一些 Ada 實現可能比 Ada 要求的精度略好,因為它們有效地利用了機器。不要編寫依賴於此的程式。
評論
[edit | edit source]指南
[edit | edit source]- 註釋程式的數值方面的分析和推導。
基本原理
[edit | edit source]關於為什麼程式中需要特定精度以及背後的決策和背景對於程式修改或移植非常重要。應該註釋導致程式的底層數值分析。
子表示式求值
[edit | edit source]指南
[edit | edit source]- 預測子表示式的值範圍,以避免超出其基本型別的值範圍。在數值型別上使用派生型別、子型別、因式分解和範圍約束(參見指南 3.4.1、5.3.1 和 5.5.3)。
例子
[edit | edit source]此示例改編自 Rationale (1995, §3.3)
with Ada.Text_IO;
with Ada.Integer_Text_IO;
procedure Demo_Overflow is
-- assume the predefined type Integer has a 16-bit range
X : Integer := 24_000;
Y : Integer;
begin -- Demo_Overflow
y := (3 * X) / 4; -- raises Constraint_Error if the machine registers used are 16-bit
-- mathematically correct intermediate result if 32-bit registers
Ada.Text_IO.Put ("(");
Ada.Integer_Text_IO.Put (X);
Ada.Text_IO.Put (" * 3 ) / 4 = ");
Ada.Integer_Text_IO.Put (Y);
exception
when Constraint_Error =>
Ada.Text_IO.Put_Line ("3 * X too big for register!");
end Demo_Overflow;
基本原理
[edit | edit source]Ada 語言不要求實現對錶達式中的子表示式進行範圍檢查。Ada 確實要求執行溢位檢查。因此,根據求值順序和暫存器的大小,子表示式將要麼溢位,要麼產生數學上正確的結果。如果發生溢位,您將獲得異常 Constraint_Error。即使程式當前目標上的實現不會導致子表示式求值溢位,您的程式也可能被移植到會導致溢位的實現。
關係測試
[edit | edit source]指南
[edit | edit source]- 考慮使用 <= 和 >= 對實值引數進行關係測試,避免使用 <、>、= 和 /= 操作。
- 在比較和檢查小值時,使用型別屬性的值。
例子
[edit | edit source]以下示例測試(1)儲存中的絕對“相等性”,(2)計算中的絕對“相等性”,(3)儲存中的相對“相等性”以及(4)計算中的相對“相等性”。
abs (X - Y) <= Float_Type'Model_Small -- (1)
abs (X - Y) <= Float_Type'Base'Model_Small -- (2)
abs (X - Y) <= abs X * Float_Type'Model_Epsilon -- (3)
abs (X - Y) <= abs X * Float_Type'Base'Model_Epsilon -- (4)
具體來說,是針對“等於” 0 的情況
abs X <= Float_Type'Model_Small -- (1)
abs X <= Float_Type'Base'Model_Small -- (2)
abs X <= abs X * Float_Type'Model_Epsilon -- (3)
abs X <= abs X * Float_Type'Base'Model_Epsilon -- (4)
基本原理
[edit | edit source]嚴格的關係比較(<、>、=、/=)是涉及實數的計算中的一般性問題。由於比較的方式是在模型區間中定義的,因此比較結果可能取決於實現。在模型區間內,如果兩個值不是模型數,則比較這兩個值的結果是非確定性的。一般來說,您應該測試鄰近度而不是相等性,如示例所示。另見 Rationale (1995, §§G.4.1 和 G.4.2)。
型別屬性是符號訪問 Ada 數值模型實現的主要手段。當透過型別屬性訪問模型數的特徵時,原始碼是可移植的。然後,生成的程式碼將使用任何實現的適當模型數。
雖然 0 從技術上講不是一個特例,但它經常被忽視,因為它看起來是最簡單,因此也是最安全的案例。但實際上,每次進行涉及小值的比較時,您都應該評估情況,以確定哪種技術是合適的。
註釋
[edit | edit source]無論語言如何,實值計算都會有誤差。相應的數學運算具有代數性質通常會引起一些混淆。本指南解釋了 Ada 如何解決大多數語言面臨的問題。
十進位制型別和資訊系統附件
[edit | edit source]指南
[edit | edit source]- 在資訊系統中,宣告不同的數值十進位制型別以對應不同的比例(Brosgol、Eachus 和 Emery 1994)。
- 建立不同十進位制型別的物件以反映不同的度量單位(Brosgol、Eachus 和 Emery 1994)。
- 宣告適當比例的十進位制型別的子型別,為特定於應用程式的型別提供適當的範圍約束。
- 將每個度量類別封裝在包中(Brosgol、Eachus 和 Emery 1994)。
- 對於無量綱資料,儘可能少地宣告十進位制型別(Brosgol、Eachus 和 Emery 1994)。
- 對於十進位制計算,確定結果應該是截斷到 0 還是四捨五入。
- 對於不支援資訊系統附件(Ada 參考手冊 1995,附件 F)的完整功能的編譯器,避免使用十進位制型別和算術運算。
例子
[edit | edit source]-- The salary cap today is $500,000; however this can be expanded to $99,999,999.99.
type Executive_Salary is delta 0.01 digits 10 range 0 .. 500_000.00;
------------------------------------------------------------------------------
package Currency is
type Dollars is delta 0.01 digits 12;
type Marks is delta 0.01 digits 12;
type Yen is delta 0.01 digits 12;
function To_Dollars (M : Marks) return Dollars;
function To_Dollars (Y : Yen) return Dollars;
function To_Marks (D : Dollars) return Marks;
function To_Marks (Y : Yen) return Marks;
function To_Yen (D : Dollars) return Yen;
function To_Yen (M : Marks) return Yen;
end Currency;
基本原理
[edit | edit source]Ada 語言沒有提供任何預定義的十進位制型別。因此,您需要為將要使用的不同比例宣告十進位制型別。在決定是否使用通用型別時,必須考慮比例和精度的差異(Brosgol、Eachus 和 Emery 1994)。
您需要不同型別的物件,這些物件以不同的單位進行測量。這使編譯器能夠檢測表示式中不匹配的值。如果您將所有十進位制物件宣告為單個型別,則您將失去強型別的優勢。例如,在涉及多種貨幣的應用程式中,每種貨幣都應該宣告為單獨的型別。您應該在不同貨幣之間提供適當的轉換。
您應該將沒有特定度量單位的資料對映到一組型別或單個型別,以避免數值型別之間轉換的激增。
將十進位制型別的範圍要求與其精度(即所需的有效位數)分開。從計劃變更和易於維護的角度來看,您可以使用數字的值來適應將來儲存在型別物件中的值的增長。例如,您可能希望預測資料庫值和報表格式的增長。您可以透過與當前需求匹配的範圍約束來約束型別的範圍。修改範圍並避免重新定義資料庫和報表更容易。
Ada 會自動截斷到 0。如果您的要求是對十進位制結果進行四捨五入,則您必須使用 'Round 屬性顯式地執行此操作。
核心語言定義了十進位制型別基本語法和操作。但是,它沒有指定必須支援的最小有效數字數量。核心語言也不要求編譯器支援 Small 的值,除了 2 的冪,從而使編譯器能夠有效地拒絕十進位制宣告(Ada 參考手冊 1995,第 3.5.9 節 [註釋])。資訊系統附錄為十進位制型別提供了額外的支援。它要求至少 18 位有效數字。它還指定了一個 Text_IO.Editing 包,它提供類似於 COBOL 圖片方法的支援。
儲存控制
[edit | edit source]動態儲存的管理在不同的 Ada 環境中可能有所不同。事實上,有些環境不提供任何釋放機制。以下 Ada 儲存控制機制是實現相關的,在編寫可移植程式時應該謹慎使用。
表示子句
[edit | edit source]指南
[edit | edit source]- 不要使用表示子句來指定儲存單元的數量。
基本原理
[edit | edit source]'Storage_Size 屬性的含義不明確;指定特定值不會提高可移植性。它可能包括或不包括為引數、資料等分配的空間。將此功能的使用保留用於必須依賴特定供應商實現的設計。
註釋
[edit | edit source]在移植活動期間,可以假設任何儲存規範的出現都表明必須重新設計的實現依賴性。
訪問子程式的值
[edit | edit source]指南
[edit | edit source]- 不要比較訪問子程式的值。
基本原理
[edit | edit source]Ada 參考手冊 1995,第 3.10.2 節 [註釋] 解釋說,“實現可能會認為兩個訪問子程式的值是不相等的,即使它們指定了同一個子程式。這是因為一個可能直接指向子程式,而另一個可能指向一個執行 Elaboration_Check 然後跳轉到子程式的特殊序言。” Ada 參考手冊 1995,第 4.5.2 節 [註釋] 指出,“對於兩個訪問值,即使它們指定了同一個子程式,但它們是 Access 屬性引用的不同評估結果,它們是相等還是不相等是未指定的。”
另請參閱指南 5.3.4。
異常
[edit | edit source]如果你必須比較訪問子程式的值,你應該使用訪問子程式的值定義一個常量,並將所有未來的比較都與該常量進行比較。但是,如果你試圖比較不同間接級別上的訪問子程式的值,即使它們指定了同一個子程式,這些值也可能仍然不相等。
儲存池機制
[edit | edit source]指南
[edit | edit source]- 考慮使用顯式定義的儲存池機制。
例子
[edit | edit source]請參閱 Ada 參考手冊 1995,第 13.11.2 節 [註釋]。
你像以前一樣使用分配器。不是使用無檢查釋放,而是維護你自己的空閒物件列表,這些物件不再使用並且可以重複使用。
你使用分配器,可能也使用無檢查釋放;但是,你實現了一個儲存池,並透過 Storage_Pool 子句將其與訪問型別相關聯。你可以使用此技術來實現一個標記/釋放儲存管理範例,這可能比分配/釋放範例快得多。一些供應商可能會在其 Ada 環境中提供一個標記/釋放包。
你不會使用分配器,而是使用地址的無檢查轉換,並完成你自己的預設初始化等。你不太可能使用最後一個選項,因為你失去了自動預設初始化。
任務
[edit | edit source]Ada 語言中對任務的定義將任務模型的許多特徵留給了實現者。這允許供應商為目標應用領域做出適當的權衡,但也降低了使用任務功能的設計和程式碼的可移植性。在某些方面,這種可移植性降低是併發方法的固有特徵(參見 Nissen 和 Wallis 1984,37)。關於在分散式目標環境中使用 Ada 任務依賴性的討論超出了本書的範圍。例如,多處理器任務排程、處理器間 rendezvous 以及透過 Calendar 包實現的分散式時間感,這些都受到不同實現之間的差異的影響。有關更多資訊,Nissen 和 Wallis (1984) 以及 ARTEWG (1986) 討論了這些問題,而 Volz 等人 (1985) 只是眾多可用的研究文章之一。
如果支援即時系統附錄,那麼許多併發方面都已完全定義,因此,程式可以依賴於這些特性,同時仍然可以移植到符合即時系統附錄的其他實現中。以下部分提供了基於缺少該附錄的指南。
任務啟用順序
[edit | edit source]指南
[edit | edit source]- 不要依賴在同一個宣告列表中宣告的任務物件啟用的順序。
基本原理
[edit | edit source]Ada 參考手冊 1995,第 9.2 節 [註釋] 中沒有定義任務物件啟用的順序。另請參閱指南 6.1.5。
延遲語句
[edit | edit source]指南
[edit | edit source]- 不要依賴於特定延遲的可實現性(Nissen 和 Wallis 1984)。
- 永遠不要使用任務的執行模式知識來實現定時要求。
基本原理
[edit | edit source]這方面的理由在指南 6.1.7 中有說明。此外,對延遲語句的處理在不同的實現之間有所不同,從而阻礙了可移植性。
使用任務執行模式知識來實現定時要求是不可移植的。Ada 沒有指定底層排程演算法,並且不能保證不同系統之間系統時鐘滴答聲始終精確。因此,當你更改系統時鐘時,你的延遲行為也會發生變化。
- 不要假設 System.Tick 與型別 Duration 之間存在關聯(參見指南 6.1.7 和 7.4.2)。
這種關聯不是必需的,儘管在某些實現中可能存在。
- 不要依賴於求值條件的順序,也不要依賴於從多個開啟的選擇分支中進行選擇的演算法。
該語言沒有定義這些條件的順序,因此假設它們是任意的。
- 不要假設任務在到達同步點之前會無間斷地執行。
- 使用 pragma Priority 僅區分一般的優先順序(參見指南 6.1.6)。
Ada 任務模型要求任務僅透過語言提供的顯式方法進行同步(即 rendezvous、任務依賴性、pragma Atomic)。排程演算法不是由語言定義的,並且可能從時間片輪轉到搶佔式優先順序。一些實現提供了幾種使用者可以選擇用於應用程式的選擇。
優先順序的數量在不同的實現之間可能有所不同。此外,即使實現使用相同的通用排程演算法,相同優先順序任務的處理方式在不同的實現之間也可能有所不同。
在即時系統中,通常需要嚴格控制任務演算法以獲得所需的效能。例如,航空電子系統通常由迴圈事件驅動,並有限的非同步中斷。非搶佔式任務模型通常用於在這些應用程式中獲得最佳效能。迴圈執行程式可以在 Ada 中程式設計,就像從迴圈到多幀速率到完全非同步的排程方案一樣(MacLaren 1980),儘管通常需要外部時鐘。
- 避免使用 abort 語句。
這在指南 6.3.3 中有說明。此外,abort 語句的處理方式因實現而異,從而阻礙了可移植性。
- 不要使用無保護的共享變數。
- 考慮使用受保護的型別來提供資料同步。
- 讓任務透過 rendezvous 機制進行通訊。
- 不要將無保護的共享變數用作任務同步裝置。
- 考慮使用受保護的物件來封裝共享資料。
- 僅在執行時系統缺陷迫使你這樣做時才使用 pragma Atomic 或 Volatile。
參見指南 6.1.1 和 6.1.2。
這在指南 6.1.1 和 6.2.4 中有說明。此外,無保護的共享變數的處理方式因實現而異,從而阻礙了可移植性。
在使用預定義異常時,應謹慎行事,因為它們處理方式的某些方面可能因實現而異。當然,必須避免特定於實現的異常。有關異常的更多資訊,請參見指南 4.3 和 5.8。有關供應商提供的功能的更多資訊,請參見指南 7.1.6。
- 不要依賴於引發預定義異常的確切位置。
- 不要依賴 Ada.Exceptions 的行為超出語言中定義的最小值。
Ada 參考手冊 1995,第 11 節 [帶註釋的])指出,在不同的實現之間,針對同一原因的預定義異常可能從不同的位置引發。你將無法區分這些異常。此外,每個預定義異常都與各種條件相關聯。針對預定義異常編寫的任何異常處理程式都必須準備好處理任何這些條件。
指南 5.6.9 討論了使用塊來定義本地異常處理程式,這些處理程式可以捕獲靠近其起源點的異常。
- 不要引發特定於實現的異常。
- 將介面包中的特定於實現的異常轉換為可見的使用者定義異常。
無法保證任何特定於實現的異常(無論它們是否來自同一供應商)都可移植到其他實現。不僅名稱可能不同,而且觸發異常的條件範圍也可能不同。
如果您為程式的特定於實現的部分建立介面包,則這些包可以捕獲或識別特定於實現的異常,並將它們轉換為在規範中宣告的使用者定義異常。當程式移植時,不要讓自己被迫查詢和更改為這些異常編寫的每個處理程式的名稱。
Ada 提供了許多依賴於實現的特性,這些特性允許比高階語言通常提供的對底層硬體體系結構的更多控制和互動。這些機制旨在幫助系統程式設計和即時程式設計,以獲得更高的效率(例如,透過表示子句獲得變數的特定大小和佈局)和直接的硬體互動(例如,中斷條目),而無需訴諸彙編級程式設計。考慮到這些特性的目標,您通常必須為使用它們付出可移植性方面的顯著代價,這一點並不奇怪。一般來說,如果可移植性是主要目標,請不要使用這些特性。當您必須使用這些特性時,將它們封裝在包中,這些包被很好地註釋為與特定目標環境介面。本節介紹了各種特性以及它們在可移植性方面的推薦用法。
- 使用不依賴於資料表示的演算法,因此不需要表示子句。
- 在訪問或定義介面資料時,或者需要特定表示來實現設計時,請考慮使用表示子句。
- 不要假設在程式之間共享原始檔就能保證這些檔案中資料型別的表示相同。
在許多情況下,使用表示子句來實現演算法很容易,即使它不是必需的。還有一種趨勢是記錄原始程式設計師對錶示的假設,以便將來參考。但不能保證另一種實現將支援所選擇的表示。不必要的表示子句還會混淆移植或維護工作,這些工作必須假設程式設計師依賴於已記錄的表示。
與外部系統和裝置的介面是需要表示子句的最常見情況。在設計和移植過程中應評估對 pragma Import 和地址子句的使用,以確定是否需要表示子句。
如果沒有表示子句,語言不要求對未更改檔案的兩次編譯導致相同的資料表示。可以改變編譯之間表示的事項包括
- 編譯順序中較早檔案的更改
- 最佳化策略或級別的更改
- 編譯器版本的更改
- 實際編譯器的更改
- 系統資源可用性的更改
因此,兩個獨立連結的程式或分割槽應只共享其表示明確控制的資料。
在移植過程中,所有表示子句都可以被評估為設計工件或訪問介面資料的規範,這些規範可能會隨著新實現而更改。
- 避免使用包 System 常量,除非在嘗試概括其他依賴於機器的構造時。
由於此包中的值是由實現提供的,因此使用它們可能會產生意想不到的效果。
如果您必須保證物理記錄佈局在不同實現之間保持一致,則可以使用其第一個和最後一個位元位置來表達記錄欄位,如 Ada 參考手冊 1995,§13.5.1 [註釋] 中所示。應使用靜態表示式和命名數字,以便編譯器能夠根據更早的欄位計算每個範圍的端點。在這種情況下,可以使用 System.Storage_Unit 使編譯器計算命名數字的值,從而實現更高的可移植性。但是,此方法可能不適用於 System.Storage_Unit 的所有值。
請使用包 System 常量來引數化其他依賴於實現的特性(參見 Pappas (1985, §13.7.1)。
- 避免機器程式碼插入。
Ada 參考手冊 (1995,附錄 C) 建議實現機器程式碼插入的包是可選的。此外,它沒有標準化,因此機器程式碼插入很可能不可移植。事實上,可能兩個不同供應商的語法對於相同的目標將有所不同,而低階細節的差異,例如暫存器約定,將阻礙可移植性。
如果必須使用機器程式碼插入來滿足另一個專案需求,請識別和記錄可移植性降低的影響。
在使用機器程式碼插入的例程主體宣告區域中,插入註釋以解釋插入提供哪些功能以及(尤其是)為什麼需要插入。透過描述對使用其他更高階構造的嘗試失敗的原因,註釋使用機器程式碼插入的必要性。
- 使用包 Interfaces 及其語言定義的子包,而不是依賴於實現的機制。
- 考慮使用 pragma Import 而不是對其他語言中的子程式使用訪問子程式型別。(最好使用“External_Name =>”引數。)
- 將所有使用 pragma Import、Export 和 Convention 的子程式隔離到特定於實現的(介面)包主體中。
此示例演示瞭如何與用 C 編寫的以下立方根函式進行介面
double cbrt (double x);
package Math_Utilities is
Argument_Error : exception;
function Cube_Root (X : Float) return Float;
...
end Math_Utilities;
------------------------------------------------------------------------------
with Interfaces.C;
package body Math_Utilities is
function Cube_Root (X : Float) return Float is
function C_Cbrt (X : Interfaces.C.Double) return Interfaces.C.Double;
pragma Import (Convention => C,
Entity => C_Cbrt,
External_Name => "cbrt");
begin
if X < 0.0 then
raise Argument_Error;
else
return Float (C_Cbrt (Interfaces.C.Double (X)));
end if;
end Cube_Root;
...
end Math_Utilities;
對於靜態介面到其他語言的子程式,pragma Import 提供了一個比訪問子程式更好的解決方案,因為它不需要間接定址。pragma Interface(1983 年 Ada 參考手冊)已被 pragma Import、Export 和 Convention 替換。Rationale(1995)的附錄 B 討論瞭如何在與其他語言介面時結合使用這些 pragma 和訪問子程式型別。
特別要注意 pragma Import 的“External_Name =>”和“Link_Name =>”引數之間的區別,這兩個引數經常被混淆。External_Name 指定子程式名稱,如其他語言(如 C 或 Fortran)的原始碼中所示。Link_Name 指定連結器使用的名稱。通常,只指定這兩個引數中的一個,並且通常 External_Name 是可移植性的首選選擇。
訪問子程式型別對於在單獨的子系統(例如 X Window 系統)中實現回撥很有用。
與外語介面的問題很複雜。這些問題包括 pragma 語法差異、將 Ada 連結/繫結到其他語言的約定以及將 Ada 變數對映到外語變數。透過將這些依賴項隱藏在介面包中,可以減少程式碼修改量。
異常
[edit | edit source]通常需要與其他語言互動,即使只使用匯編語言來訪問某些硬體功能。在這種情況下,清楚地註釋介面的要求和限制以及 pragma Import、Export 和 Conventions 的使用。
實現特定的 Pragma 和屬性
[edit | edit source]指南
[edit | edit source]- 避免編譯器實現者新增的 pragma 和屬性。
基本原理
[edit | edit source]Ada 參考手冊 (1995) 允許實現者新增 pragma 和屬性來利用特定的硬體架構或軟體環境。這些顯然比實現者對預定義 pragma 和屬性的解釋更具實現特定性,因此可移植性更差。但是,Ada 參考手冊 (1995) 定義了一組附錄,它們對某些特殊需求(即即時系統、分散式系統、資訊系統、數值、與外語介面以及安全性和安全性)採取了統一一致的方法。您應該始終優先選擇附錄中定義的功能,而不是任何供應商定義的 pragma 和屬性。
未檢查的釋放
[edit | edit source]指南
[edit | edit source]- 避免依賴於 Ada.Unchecked_Deallocation(參見指南 5.9.2)。
基本原理
[edit | edit source]未檢查的儲存釋放機制是用於覆蓋分配儲存被回收的預設時間的其中一種方法。最早的預設時間是當物件不再可訪問時,例如,當控制離開宣告訪問型別的範圍時(此時間之後的確切點取決於實現)。如果在此時之前執行了對儲存的任何未檢查的釋放,則如果嘗試訪問該物件,可能會導致 Ada 程式出錯。
本指南比指南 5.9.2 更嚴格,因為對 Ada.Unchecked_Deallocation 的實現具有極強的依賴性。使用它可能會導致可移植性方面出現相當大的困難。
註釋
[edit | edit source]Ada.Unchecked_Deallocation 是所有 Ada 實現中支援的功能。可移植性問題在於,未檢查的儲存釋放可能會在不同的實現中導致不同的結果。
異常
[edit | edit source]在對高度迭代或遞迴演算法進行區域性控制時,使用未檢查的儲存釋放可能會有益,因為在這種情況下,可用儲存可能會超過。
未檢查的訪問
[edit | edit source]指南
[edit | edit source]- 避免依賴於屬性 Unchecked_Access(參見指南 5.9.2)。
基本原理
[edit | edit source]訪問值受可訪問性限制。使用屬性 Unchecked_Access 會阻止檢查這些規則,並且程式設計師可能會遇到懸掛引用。
未檢查的轉換
[edit | edit source]指南
[edit | edit source]- 避免依賴於 Ada.Unchecked_Conversion(參見指南 5.9.1)。
基本原理
[edit | edit source]未檢查的型別轉換機制實際上是一種繞過 Ada 中強型別設施的方法。實現可以自由限制可以匹配的型別以及當物件大小不同時發生的結果。
異常
[edit | edit source]未檢查的型別轉換在 Ada 程式的實現特定部分很有用,在這些部分中,可移植性被隔離,低階程式設計和外語介面是目標。
如果使用了列舉表示子句,未檢查的型別轉換是檢索列舉值的內部整型程式碼的唯一語言提供的方式。
執行時依賴項
[edit | edit source]指南
[edit | edit source]- 避免直接呼叫或隱式依賴底層主機作業系統或 Ada 執行時支援系統,除非該介面在語言中明確定義(例如,Ada 參考手冊 [1995] 的附錄 C 或 D)。
- 當您需要呼叫底層執行時支援系統時,請使用標準繫結和包 Ada.Command_Line。
- 使用附錄中定義的功能,而不是供應商定義的功能。
基本原理
[edit | edit source]Ada 參考手冊 (1995) 中未指定的實現功能通常在不同實現之間會有所不同。特定的實現依賴功能不太可能在其他實現中提供。除了強制性的預定義語言環境外,附錄還定義了各種包、屬性和編譯指示,以規範化幾個專門領域的實現依賴功能。當您使用附錄中包中宣告的功能時,您可以提高可移植性,因為您可以將您的程式移植到實現您所使用相同附錄的其他供應商環境中。即使大多數供應商最終提供了類似的功能,他們也不太可能具有相同的公式。事實上,不同的供應商可能使用相同的公式來表示(語義上)完全不同的功能。
在編碼時,請儘量避免依賴底層作業系統。考慮在主機開發系統上將系統呼叫包含在程式中的後果。如果這些呼叫沒有被標記為刪除和替換,那麼程式可能會經過開發和測試,但當移動到目標環境中時,由於目標環境缺少主機上這些系統呼叫提供的功能,程式將無法使用。
指南 7.1.5 討論了 Ada.Command_Line 包的使用。如果 Ada 環境實現了對作業系統服務的標準繫結,例如 POSIX/Ada,並且您編寫了符合 POSIX 的呼叫,那麼您的程式應該可以在更多系統之間移植。
在即時嵌入式系統中,呼叫低階支援系統設施通常是不可避免的。隔離這些設施的使用可能過於困難。對它們進行註釋,就像您對機器程式碼插入進行註釋一樣(參見指南 7.6.3);在某種程度上,它們是針對支援系統提供的虛擬機器提供的指令。當隔離這些功能的使用時,請為您的程式的其他部分提供一個介面,該介面可以透過替換介面的實現來移植。
Ada 中的 I/O 設施不是語言的語法定義的一部分。語言中的構造已被用於為此目的定義一組包。這些包預計不會滿足所有應用程式(特別是嵌入式系統)的所有 I/O 需求。它們充當核心子集,可以在簡單資料上使用,也可以用作在語言提供的低階構造之上構建 I/O 設施的示例。提供一個能夠滿足所有應用程式要求並與許多現有作業系統整合的 I/O 定義會導致不可接受的實現依賴性。在與主機作業系統一起執行的應用程式與 Ada 執行時自給自足的嵌入式目標之間,遇到的可移植性問題型別往往有所不同。與主機作業系統互動增加了與主機檔案系統結構(例如層次目錄)、訪問方法(例如索引順序訪問方法 [ISAM])和命名約定(例如基於當前目錄的邏輯名稱和別名)共存的複雜性。ARTEWG(1986)中的輸入/輸出部分提供了一些此類依賴性的示例。嵌入式應用程式具有不同的依賴性,這些依賴性通常將它們繫結到其硬體裝置的低階細節。
針對 I/O 中這些固有的實現依賴性的主要防禦措施是嘗試在任何給定應用程式中隔離其功能。以下大多數指南都側重於此方向。
- 在預定義的 I/O 包上,使用常量和變數作為名稱和形式引數的符號實際引數。在實現依賴包中宣告和初始化它們。
預定義的 I/O 包上這些引數的格式和允許值在不同實現之間可能差異很大。隔離這些值有助於提高可移植性。不指定形式字串或使用空值並不能保證可移植性,因為實現可以自由指定預設值。
可能希望透過定義其他 Create 和 Open 過程來進一步抽象 I/O 設施,這些過程完全隱藏了 Form 引數的可見性(參見 Pappas 1985,54-55)。
- 顯式關閉所有檔案。
Ada 參考手冊 1995,第 A.7 節 [註釋] 沒有定義主子程式完成之後外部檔案會發生什麼情況(特別是如果相應的檔案沒有被關閉)。
關閉的臨時檔案的處置可能會有所不同,這可能會影響效能和空間可用性(ARTEWG 1986)。
- 避免對訪問型別執行 I/O。
Ada 參考手冊 1995,第 A.7 節 [註釋] 沒有指定對訪問型別執行 I/O 的效果。寫入此類值時,它將超出實現的範圍。因此,它超出了強型別檢查的可靠性增強控制的範圍。
考慮此操作的含義。訪問型別的值的可能實現之一是虛擬地址。如果寫入此類值,如何才能期望另一個程式讀取該值並對其進行任何合理的利用?該值不能被解釋為引用讀取器地址空間中的任何有意義的位置,也不能透過讀取的值來推斷關於寫入器地址空間的任何資訊。後者與寫入器嘗試解釋或使用該值時會遇到的問題相同,如果該值被讀回。也就是說,垃圾收集和/或堆壓縮方案可能已移動了以前由該值訪問的專案,從而使該值“指向”現在由底層實現以不可確定方式使用的空間。
- 除非您需要 Stream_IO 提供的低階異構 I/O 功能,否則請考慮使用 Sequential_IO 或 Direct_IO 而不是 Stream_IO。
Sequential_IO 和 Direct_IO 仍然非常適合處理同構檔案。此外,在打算處理同構檔案的情況下,使用 Sequential_IO 或 Direct_IO 的好處是可以在編譯時強制執行此意圖。
Stream_IO 應保留用於處理異構檔案。在這種情況下,檔案不是所有型別都相同的物件的序列,而是不同型別物件的序列。為了以正確的順序讀取異構物件序列,需要一些特定於應用程式的知識。
- 考慮使用 Current_Error 和 Set_Error 來處理執行時錯誤訊息。
with Ada.Text_IO;
...
begin
Ada.Text_IO.Open (File => Configuration_File,
Mode => Ada.Text_IO.In_File,
Name => Configuration_File_Name);
exception
when Ada.Text_IO.Name_Error =>
Ada.Text_IO.Put_Line (File => Ada.Text_IO.Standard_Error,
Item => "Can't open configuration file.");
...
end;
Text_IO 包包含當前錯誤檔案概念。您應該透過關聯的子程式 Current_Error 和 Set_Error 向用戶報告錯誤,而不是使用標準輸出工具。在互動式應用程式中,使用 Text_IO 錯誤工具可以提高使用者介面的可移植性。
在具有多個 I/O 任務的程式中,您需要注意多個任務嘗試設定 Current_Input、Current_Output 或 Current_Error 的情況。潛在問題在於對與包關聯的“共享”狀態的無保護更新,在本例中為 Text_IO 包。準則 6.1.1 和 6.2.4 討論了與無保護共享變數相關的相關問題。
- 在旨在具有較長生命週期的程式或元件中,避免使用 Ada 參考手冊(1995)的附錄 J 宣告為“過時”的 Ada 特性,除非需要使用該特性才能向後相容 Ada 83(Ada 參考手冊 1983)。
- 記錄任何過時特性的使用情況。
- 避免使用以下特性
- 預定義環境中包的簡短重新命名(例如,Text_IO 而不是 Ada.Text_IO)
- 對 ! 替代為 |, : 替代為 #,以及 % 替代為引號的字元替換
- 浮點型別的精度降低的子型別
- 應用於私有型別的'Constrained 屬性
- 預定義的包 ASCII
- 異常 Numeric_Error
- 各種表示規範,包括 at 子句、mod 子句、中斷入口和 Storage_Size 屬性
- 對以下內容在潛在目標平臺上提供的支援做出明智的假設
- 整數型別可用位數(範圍約束)
- 浮點型別可用的精度小數位數
- 定點型別可用位數(增量和範圍約束)
- 源文字每行字元數
- Root_Integer 表示式位數
- Duration 範圍內的秒數
- Duration'Small 的毫秒數
- 十進位制型別的最小和最大比例
- 避免對 Character 型別中包含的值和值的數量做出假設。
- 對每個包、子程式和任務使用突出顯示的註釋,其中存在任何不可移植的特性。
- 對於每個使用的不可移植特性,描述對該特性的期望。
- 考慮僅使用無引數過程作為主子程式。
- 考慮使用 Ada.Command_Line 訪問環境中的值,但要注意此包的行為甚至其規範都是不可移植的。
- 封裝和記錄所有對包 Ada.Command_Line 的使用。
- 建立專門設計的包,以隔離硬體和實現依賴,並且這些包的規範在移植時不會發生變化。
- 如果硬體或實現相關的程式碼是出於機器或解決方案效率的原因,則要明確說明目標。
- 對於隱藏實現依賴關係的包,為不同的目標環境維護不同的包體。
- 將中斷接收任務隔離到實現相關的包中。
- 有關實現相關的功能列表,請參見 Ada 參考手冊(1995 年)的附件 M。
- 避免使用供應商提供的包。
- 避免使用新增到預定義包中的功能,這些功能未在 Ada 語言定義或專門需求附件中指定。
- 使用專門需求附件中定義的功能,而不是供應商定義的功能。
- 清楚地記錄對來自專門需求附件的任何功能的使用(系統程式設計、即時系統、分散式系統、資訊系統、數值和安全與安全性)。
- 不要編寫其正確執行依賴於實現使用的特定引數傳遞機制的程式碼(Ada 參考手冊 1995 年,§6.2 [Annotated];Cohen 1986)。
- 如果子程式具有多個給定子型別的形式引數,其中至少一個為 [in] out,則確保子程式能夠正確處理兩個形式引數都表示同一實際物件的情況。
- 避免依賴於 Ada 中某些構造的評估順序。
- 避免使用 Standard 包中的預定義數值型別。使用範圍和位數宣告,並讓實現選擇合適的表示形式。
- 對於需要比全域性假設提供的精度更高的程式,請定義一個包,該包宣告私有型別和操作,具體需求見Pappas(1985),其中有完整解釋和示例。
- 考慮對以下內容使用預定義的數值型別(Integer,Natural,Positive):
- 陣列的索引,其中索引型別並不重要,例如String型別
- “純”數字,即沒有關聯物理單位的數字(例如,指數)
- 用於控制重複或迭代計數的值
- 當效能和精度是首要問題時,請使用支援 Numerics 附錄 (Ada 參考手冊 1995,附錄 G) 的實現。
- 仔細分析您真正需要的精度。
- 不要逼近機器的精度極限。
- 註釋程式的數值方面的分析和推導。
- 預測子表示式的值範圍,以避免超出其基本型別的底層範圍。使用派生型別、子型別、分解和數值型別的範圍約束。
- 考慮使用 <= 和 >= 對實值引數進行關係測試,避免使用 <、>、= 和 /= 操作。
- 在比較和檢查小值時,使用型別屬性的值。
- 在資訊系統中,宣告不同的數值十進位制型別以對應不同的比例(Brosgol、Eachus 和 Emery 1994)。
- 建立不同十進位制型別的物件以反映不同的度量單位(Brosgol、Eachus 和 Emery 1994)。
- 宣告適當比例的十進位制型別的子型別,為特定於應用程式的型別提供適當的範圍約束。
- 將每個度量類別封裝在包中(Brosgol、Eachus 和 Emery 1994)。
- 對於無量綱資料,儘可能少地宣告十進位制型別(Brosgol、Eachus 和 Emery 1994)。
- 對於十進位制計算,確定結果應該是截斷到 0 還是四捨五入。
- 對於不支援資訊系統附件(Ada 參考手冊 1995,附件 F)的完整功能的編譯器,避免使用十進位制型別和算術運算。
- 不要使用表示子句來指定儲存單元的數量。
- 不要比較訪問子程式的值。
- 考慮使用顯式定義的儲存池機制。
- 不要依賴在同一個宣告列表中宣告的任務物件啟用的順序。
- 不要依賴於特定延遲的可實現性(Nissen 和 Wallis 1984)。
- 永遠不要使用任務的執行模式知識來實現定時要求。
- 不要假設 System.Tick 和 Duration 型別之間存在關聯。
- 不要依賴於求值條件的順序,也不要依賴於從多個開啟的選擇分支中進行選擇的演算法。
- 不要假設任務在到達同步點之前會無間斷地執行。
- 使用 pragma Priority 僅區分一般重要性級別。
- 避免使用 abort 語句。
- 不要使用無保護的共享變數。
- 考慮使用受保護的型別來提供資料同步。
- 讓任務透過 rendezvous 機制進行通訊。
- 不要將無保護的共享變數用作任務同步裝置。
- 考慮使用受保護的物件來封裝共享資料。
- 僅在執行時系統缺陷迫使你這樣做時才使用 pragma Atomic 或 Volatile。
- 不要依賴於引發預定義異常的確切位置。
- 不要依賴 Ada.Exceptions 的行為超出語言中定義的最小值。
- 不要引發特定於實現的異常。
- 將介面包中的特定於實現的異常轉換為可見的使用者定義異常。
- 使用不依賴於資料表示的演算法,因此不需要表示子句。
- 訪問或定義介面資料時,或需要特定表示形式來實現設計時,請考慮使用表示子句。
- 不要假設在程式之間共享原始檔就能保證這些檔案中資料型別的表示相同。
- 避免使用包 System 常量,除非在嘗試概括其他依賴於機器的構造時。
- 避免機器程式碼插入。
- 使用包 Interfaces 及其語言定義的子包,而不是依賴於實現的機制。
- 考慮使用 pragma Import 而不是指向子程式型別的訪問,以便與其他語言中的子程式進行介面。
- 將所有使用 pragma Import、Export 和 Convention 的子程式隔離到特定於實現的(介面)包主體中。
- 避免編譯器實現者新增的 pragma 和屬性。
- 避免依賴 Ada.Unchecked_Deallocation。
- 避免依賴 Unchecked_Access 屬性。
- 避免依賴 Ada.Unchecked_Conversion。
- 避免直接呼叫或隱式依賴底層主機作業系統或 Ada 執行時支援系統,除非該介面在語言中明確定義(例如,Ada 參考手冊 [1995] 的附錄 C 或 D)。
- 當您需要呼叫底層執行時支援系統時,請使用標準繫結和 Ada.Command_Line 包。
執行時支援系統。
- 使用附錄中定義的功能,而不是供應商定義的功能。
- 在預定義的 I/O 包上,使用常量和變數作為名稱和形式引數的符號實際引數。在實現依賴包中宣告和初始化它們。
- 顯式關閉所有檔案。
- 避免對訪問型別執行 I/O。
- 除非您需要 Stream_IO 提供的低階異構 I/O 功能,否則請考慮使用 Sequential_IO 或 Direct_IO 而不是 Stream_IO。
- 考慮使用 Current_Error 和 Set_Error 來處理執行時錯誤訊息。