Some language features are often misunderstood, resulting in common programming errors, performance degradation and portability problems. The following incorrect usages of the Ada language are often seen in code written by Ada beginners.
pragma Atomic & Volatile
It is almost always incorrect to use atomic or volatile variables for tasking. When an object is atomic it just means that it will be read from or written to memory atomically. The compiler will not generate atomic instructions or memory barriers when accessing to that object, it will just:
- check that the architecture guarantees atomic memory loads and stores,
- disallow some compiler optimizations, like reordering or suppressing redundant accesses to the object.
For example, the following code, where A is an atomic object can be misunderstood:
A := A + 1; -- Not an atomic increment!
The compiler will not (and is not allowed by the Standard to) generate an atomic increment instruction to directly increment and update from memory the variable A. This is the code generated by the compiler:
A := A + 1; 804969f: a1 04 95 05 08 mov 0x8059504,%eax 80496a4: 40 inc %eax 80496a5: a3 04 95 05 08 mov %eax,0x8059504
As can be seen, no atomic increment instruction or test-and-set opcode will be generated. Like in other programming languages, if these specific instructions are required in the program they must be written explicitly using machine code insertions.
The above code snippet is equivalent to the following code (both code sequences generates exactly the same object code), where T is a (non-atomic) temporary variable:
T := A; -- A is copied atomically to local variable T T := T + 1; -- local variable T is incremented A := T; -- A is stored atomically
Thus it is incorrect to modify an atomic variable at the same time from multiple tasks. For example, two tasks incrementing a counter in parallel. Even in an uniprocessor, other Ada tasking features like a protected object should be used instead. In multiprocessors, depending on the memory consistency model, using various atomic or volatile variables for task communication can have surprising consequences. Therefore, extreme care should be taken when using atomic objects for task data sharing or synchronization, specially in a multiprocessor.
- Arch Robison (2007-11-30). "Volatile: Almost Useless for Multi-Threaded Programming". Intel Software Network. http://softwareblogs.intel.com/2007/11/30/volatile-almost-useless-for-multi-threaded-programming/. Retrieved 2008-05-30. "There is a widespread notion that the keyword volatile is good for multi-threaded programming (...) volatile is almost useless for multi-threaded programming."
- Ian Lance Taylor (2008-03-05). "Volatile". http://www.airs.com/blog/archives/154. Retrieved 2008-05-28. "Using [the C/C++ qualifier] volatile does not mean that the variable is accessed atomically; no locks are used. Using volatile does not mean that other cores in a multi-core system will see the memory accesses; no cache flushes are used. (...) Using volatile does not imply any sort of memory barrier; the processor can and will rearrange volatile memory accesses. (...) You should not use more than one such variable to communicate between any pair of threads, as there is no guarantee that the different threads will see the accesses in the same order."
- Laurent Guerby (January 1995). "C.5 Shared Variable Control". Ada 95 Rationale. Intermetrics. "A need to access specific machine instructions arises sometimes (...). Examples include instructions that perform compound operations atomically on shared memory, such as test-and-set and compare-and-swap (...)"
- Sarita V. Adve, Kourosh Gharachorloo (December 1996). "Shared Memory Consistency Models: A Tutorial". IEEE Computer 29 (12): 66–76. http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-95-7.pdf. Retrieved 2008-05-28.
Exact data representation
It is important to realize that pragma Pack must not be used to specify the exact representation of a data type, but to help the compiler to improve the efficiency of the generated code. The compiler is free to ignore the pragma, therefore if a specific representation of a type is required, representation clauses should be used instead (record representation clauses, and/or attributes 'Size or 'Component_Size).
Although in Ada 83 packed boolean arrays were used for bit-wise operations, since Ada 95 modular types are more adequate for these operations. The argument may be weighed against the advantages of named Boolean array indexes such as Traffic_Lights'(Red => True,
others => False), depending on use case.
- Adam Beneschan (2008-01-09). "Pragma Pack vs. Convention C, portability issue?". comp.lang.ada. (Web link). Retrieved on 2008-05-27.
- Software Productivity Consortium (October 1995). Ada 95 Quality and Style Guide, "10.5.7 Packed Boolean Array Shifts"
- Software Productivity Consortium (October 1995). Ada 95 Quality and Style Guide, "10.6.3 Bit Operations on Modular Types"
The 'Bit_Order attribute is not intended to convert data between a big-endian and a little-endian machine (it affects bit numbering, not byte order). The compiler will not generate code to reorder multi-byte fields when a non-native bit order is specified.
- AI95-00133-01 (1996-05-07). "Controlling bit ordering". Class: binding interpretation. Ada Rapporteur Group. http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ais/ai-00133.txt?rev=1.17. "Bit_Order clauses are concerned with the numbering of bits and not concerned with data flipping interoperability."
- ISO/IEC 8652:2007. "13.5.3 Bit Ordering (9/2)". Ada 2005 Reference Manual. http://www.adaic.com/standards/05rm/html/RM-13-5-3.html#I4589. Retrieved 2008-06-02. "Bit_Order clauses make it possible to write record_representation_clauses that can be ported between machines having different bit ordering. They do not guarantee transparent exchange of data between such machines."
- Thomas Quinot (January 2013). "Gem #140: Bridging the Endianness Gap". AdaCore. http://www.adacore.com/adaanswers/gems/gem-140-bridging-the-endianness-gap/. Retrieved 2013-01-31. "the order in which the bytes that constitute machine scalars are written to memory is not changed by the Bit_Order attribute -- only the indices of bits within machine scalars are changed."
A common Ada programming mistake is to assume that specifying 'Size for a type T forces the compiler to allocate exactly this number of bits for objects of this type. This is not true. The specified T'Size will force the compiler to use this size for components in packed arrays and records and in Unchecked_Conversion, but the compiler is still free to allocate more bits for stand-alone objects.
Use 'Size on the object itself to force the object to the specified value.