7.5 Representation Clauses And Implementation-Dependent Features
Ada provides many implementation-dependent features that permit greater control over and interaction with the underlying hardware architecture than is normally provided by a high-order language. These mechanisms are intended to assist in systems programming and real-time programming to obtain greater efficiency (e.g., specific size and layout of variables through representation clauses) and direct hardware interaction (e.g., interrupt entries) without having to resort to assembly level programming. Given the objectives for these features, it is not surprising that you must usually pay a significant price in portability to use them. In general, where portability is the main objective, do not use these features. When you must use these features, encapsulate them in packages that are well-commented as interfacing to the particular target environment. This section identifies the various features and their recommended use with respect to portability.
Representation Clauses
guideline
- Use algorithms that do not depend on the representation of the data and, therefore, do not need representation clauses.
- Consider using representation clauses when accessing or defining interface data or when a specific representation is needed to implement a design.
- Do not assume that sharing source files between programs guarantees the same representation of data types in those files.
rationale
In many cases, it is easy to use representation clauses to implement an algorithm, even when it is not necessary. There is also a tendency to document the original programmer's assumptions about the representation for future reference. But there is no guarantee that another implementation will support the representation chosen. Unnecessary representation clauses also confuse porting or maintenance efforts, which must assume that the programmer depends on the documented representation.
Interfaces to external systems and devices are the most common situations where a representation clause is needed. Uses of pragma Import and address clauses should be evaluated during design and porting to determine whether a representation clause is needed.
Without representation clauses, the language does not require two compilations of an unchanged file to result in the same data representation. Things that can change the representation between compilations include:
- A change in a file earlier in the compilation order
- A change in the optimization strategy or level
- A change in versions of the compiler
- A change in actual compilers
- A change in the availability of system resources
Therefore, two independently linked programs or partitions should only share data that has their representations explicitly controlled.
notes
During a porting effort, all representation clauses can be evaluated as either design artifacts or specifications for accessing interface data that might change with a new implementation.
Package System
guideline
- Avoid using package System constants except in attempting to generalize other machine-dependent constructs.
rationale
Because the values in this package are implementation-provided, unexpected effects can result from their use.
notes
If you must guarantee that physical record layouts will remain the same between implementations, you can express record fields by their first and last bit positions as shown in the Ada Reference Manual (1995, §13.5.1). Static expressions and named numbers should be used to let the compiler compute the endpoints of each range in terms of earlier fields. In this case, greater portability can be achieved by using System.Storage_Unit to let the compiler compute the value of the named number. However, this method might not work for all values of System.Storage_Unit.
exceptions
Do use package System constants to parameterize other implementation-dependent features (see Pappas (1985, §13.7.1).
Machine Code Inserts
guideline
- Avoid machine code inserts.
rationale
The Ada Reference Manual (1995, Annex C) suggests that the package that implements machine code inserts is optional. Additionally, it is not standardized so that machine code inserts are most likely not portable. In fact, it is possible that two different vendors' syntax will differ for an identical target, and differences in lower-level details, such as register conventions, will hinder portability.
exceptions
If machine code inserts must be used to meet another project requirement, recognize and document the portability decreasing effects.
In the declarative region of the body of the routine where machine code inserts are being used, insert comments explaining what functions inserts provide and (especially) why the inserts are necessary. Comment the necessity of using machine code inserts by delineating what went wrong with attempts to use other higher level constructs.
Interfacing to Foreign Languages
guideline
- Use the package Interfaces and its language-defined child packages rather than implementation-specific mechanisms.
- Consider using pragma Import rather than access-to-subprogram types for interfacing to subprograms in other languages. (Preferably using the "External_Name =>" argument.)
- Isolate all subprograms employing pragmas Import, Export, and Convention to implementation-specific (interface) package bodies.
example
This example shows how to interface with the following cube root function written in 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;
rationale
For static interfacing to subprograms in other languages, the pragma Import provides a better solution than access to subprograms because no indirection is required. The pragma Interface (Ada Reference Manual 1983) has been replaced by pragmas Import, Export, and Convention. Annex B of the Rationale (1995) discusses how to use these pragmas in conjunction with the access-to-subprogram types in interfacing to other languages.
Note especially the distinction between the "External_Name =>" and "Link_Name =>" parameters to pragma Import which are frequently confused. External_Name specifies the procedure name as it appears in the source code of the other language (such as C or Fortran). Link_Name specifies the name used by the linker. Typically, only one of these parameters is specified, and generally External_Name is the preferred choice for portability.
Access to subprogram types is useful for implementing callbacks in a separate subsystem, such as the X Window system.
The problems with interfacing to foreign languages are complex. These problems include pragma syntax differences, conventions for linking/binding Ada to other languages, and mapping Ada variables to foreign language variables. By hiding these dependencies within interface packages, the amount of code modification can be reduced.
exceptions
It is often necessary to interact with other languages, if only an assembly language, to reach certain hardware features. In these cases, clearly comment the requirements and limitations of the interface and pragma Import, Export, and Conventions usage.
Implementation-Specific Pragmas and Attributes
guideline
- Avoid pragmas and attributes added by the compiler implementor.
rationale
The Ada Reference Manual (1995) permits an implementor to add pragmas and attributes to exploit a particular hardware architecture or software environment. These are obviously even more implementation-specific and therefore less portable than an implementor's interpretations of the predefined pragmas and attributes. However, the Ada Reference Manual (1995) defines a set of annexes that have a uniform and consistent approach to certain specialized needs, namely, real-time systems, distributed systems, information systems, numerics, interfacing to foreign languages, and safety and security. You should always prefer the facilities defined in the annexes to any vendor-defined pragmas and attributes.
Unchecked Deallocation
guideline
- Avoid dependence on Ada.Unchecked_Deallocation (see Guideline 5.9.2).
rationale
The unchecked storage deallocation mechanism is one method for overriding the default time at which allocated storage is reclaimed. The earliest default time is when an object is no longer accessible, for example, when control leaves the scope where an access type was declared (the exact point after this time is implementation-dependent). Any unchecked deallocation of storage performed prior to this may result in an erroneous Ada program if an attempt is made to access the object.
This guideline is stronger than Guideline 5.9.2 because of the extreme dependence on the implementation of Ada.Unchecked_Deallocation. Using it could cause considerable difficulty with portability.
notes
Ada.Unchecked_Deallocation is a supported feature in all Ada implementations. The portability issue arises in that unchecked storage deallocations might cause varying results in different implementations.
exceptions
Using unchecked deallocation of storage can be beneficial in local control of highly iterative or recursive algorithms where available storage may be exceeded.
Unchecked Access
guideline
- Avoid dependence on the attribute Unchecked_Access (see Guideline 5.9.2).
rationale
Access values are subject to accessibility restrictions. Using the attribute Unchecked_Access prevents these rules from being checked, and the programmer runs the risk of having dangling references.
Unchecked Conversion
guideline
- Avoid dependence on Ada.Unchecked_Conversion (see Guideline 5.9.1).
rationale
The unchecked type conversion mechanism is, in effect, a means of bypassing the strong typing facilities in Ada. An implementation is free to limit the types that may be matched and the results that occur when object sizes differ.
exceptions
Unchecked type conversion is useful in implementation-dependent parts of Ada programs where lack of portability is isolated and where low-level programming and foreign language interfacing are the objectives.
If an enumeration representation clause is used, unchecked type conversion is the only language-provided way to retrieve the internal integer code of an enumeration value.
Run-Time Dependencies
guideline
- Avoid the direct invocation of or implicit dependence upon an underlying host operating system or Ada run-time support system, except where the interface is explicitly defined in the language (e.g., Annex C or D of the Ada Reference Manual [1995]).
- Use standard bindings and the package Ada.Command_Line when you need to invoke the underlying run-time support system.
- Use features defined in the Annexes rather than vendor-defined features.
rationale
Features of an implementation not specified in the Ada Reference Manual (1995) will usually differ between implementations. Specific implementation-dependent features are not likely to be provided in other implementations. In addition to the mandatory predefined language environment, the annexes define various packages, attributes, and pragmas to standardize implementation-dependent features for several specialized domains. You enhance portability when you use the features declared in the packages in the Annexes because you can port your program to other vendor environments that implement the same Annexes you have used. Even if a majority of vendors eventually provide similar features, they are unlikely to have identical formulations. Indeed, different vendors may use the same formulation for (semantically) entirely different features.
When coding, try to avoid depending on the underlying operating system. Consider the consequences of including system calls in a program on a host development system. If these calls are not flagged for removal and replacement, the program could go through development and testing only to be unusable when moved to a target environment that lacks the facilities provided by those system calls on the host.
Guideline 7.1.5 discusses the use of the package Ada.Command_Line. If an Ada environment implements a standard binding to operating system services, such as POSIX/Ada, and you write POSIX-compliant calls, your program should be portable across more systems.
exceptions
In real-time, embedded systems, making calls to low-level support system facilities may often be unavoidable. Isolating the uses of these facilities may be too difficult. Comment them as you would machine code inserts (see Guideline 7.6.3); they are, in a sense, instructions for the virtual machine provided by the support system. When isolating the uses of these features, provide an interface for the rest of your program to use, which can be ported through replacement of the interface's implementation.