# Types and Representation

## Enumeration Representation Clauses

We have talked about the internal code of an enumeration in another section. We may change this internal code by using a representation clause, which has the following format:

```for Primary_Color is (Red   =>    1,
Green =>    5,
Blue  => 1000);
```

The value of each code in a representation clause must be distinct. However, as you can see above, we don't need to use sequential values — the values must, however, increase for each enumeration.

We can rewrite the previous example using a representation clause:

```

package Days is

type Day is (Mon, Tue, Wed,
Thu, Fri,
Sat, Sun);

for Day use (Mon => 2#00000001#,
Tue => 2#00000010#,
Wed => 2#00000100#,
Thu => 2#00001000#,
Fri => 2#00010000#,
Sat => 2#00100000#,
Sun => 2#01000000#);

end Days;

with Days;        use Days;

procedure Show_Days is
begin
for D in Day loop
Put_Line (Day'Image (D)
& " position      = "
& Integer'Image (Day'Pos (D)));
Put_Line (Day'Image (D)
& " internal code = "
& Integer'Image
(Day'Enum_Rep (D)));
end loop;
end Show_Days;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Now, the value of the internal code is the one that we've specified in the representation clause instead of being equivalent to the value of the enumeration position.

In the example above, we're using binary values for each enumeration — basically viewing the integer value as a bit-field and assigning one bit for each enumeration. As long as we maintain an increasing order, we can use totally arbitrary values as well. For example:

```

package Days is

type Day is (Mon, Tue, Wed,
Thu, Fri,
Sat, Sun);

for Day use (Mon =>  5,
Tue =>  9,
Wed => 42,
Thu => 49,
Fri => 50,
Sat => 66,
Sun => 99);

end Days;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

## Data Representation

This section provides a glimpse on attributes and aspects used for data representation. They are usually used for embedded applications because of strict requirements that are often found there. Therefore, unless you have very specific requirements for your application, in most cases, you won't need them. However, you should at least have a rudimentary understanding of them. To read a thorough overview on this topic, please refer to the Introduction to Embedded Systems Programming course.

### Sizes

Ada offers multiple attributes to retrieve the size of a type or an object:

Attribute

Description

`Size`

Size of the representation of a subtype or an object.

`Object_Size`

Size of a component or an aliased object.

`Component_Size`

Size of a component of an array.

`Storage_Size`

Number of storage elements reserved for an access type or a task object.

For the first three attributes, the size is measured in bits. In the case of `Storage_Size`, the size is measured in storage elements. Note that the size information depends your target architecture. We'll discuss some examples to better understand the differences among those attributes.

#### Size attribute and aspect

Let's start with a code example using the `Size` attribute:

```

package Custom_Types is

type UInt_7 is range 0 .. 127;

type UInt_7_S32 is range 0 .. 127
with Size => 32;

end Custom_Types;

with Custom_Types; use Custom_Types;

procedure Show_Sizes is
V1 : UInt_7;
V2 : UInt_7_S32;
begin
Put_Line ("UInt_7'Size:            "
& UInt_7'Size'Image);
Put_Line ("UInt_7'Object_Size:     "
& UInt_7'Object_Size'Image);
Put_Line ("V1'Size:                "
& V1'Size'Image);
New_Line;

Put_Line ("UInt_7_S32'Size:        "
& UInt_7_S32'Size'Image);
Put_Line ("UInt_7_S32'Object_Size: "
& UInt_7_S32'Object_Size'Image);
Put_Line ("V2'Size:                "
& V2'Size'Image);
end Show_Sizes;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Depending on your target architecture, you may see this output:

```UInt_7'Size:             7
UInt_7'Object_Size:      8
V1'Size:                 8

UInt_7_S32'Size:         32
UInt_7_S32'Object_Size:  32
V2'Size:                 32
```

When we use the `Size` attribute for a type `T`, we're retrieving the minimum number of bits necessary to represent objects of that type. Note that this is not the same as the actual size of an object of type `T` because the compiler will select an object size that is appropriate for the target architecture.

In the example above, the size of the `UInt_7` is 7 bits, while the most appropriate size to store objects of this type in the memory of our target architecture is 8 bits. To be more specific, the range of `UInt_7` (0 .. 127) can be perfectly represented in 7 bits. However, most target architectures don't offer 7-bit registers or 7-bit memory storage, so 8 bits is the most appropriate size in this case.

We can retrieve the size of an object of type `T` by using the `Object_Size`. Alternatively, we can use the `Size` attribute directly on objects of type `T` to retrieve their actual size — in our example, we write `V1'Size` to retrieve the size of `V1`.

In the example above, we've used both the `Size` attribute (for example, `UInt_7'Size`) and the `Size` aspect (`with Size => 32`). While the size attribute is a function that returns the size, the size aspect is a request to the compiler to verify that the expected size can be used on the target platform. You can think of this attribute as a dialog between the developer and the compiler:

(Developer) "I think that `UInt_7_S32` should be stored using at least 32 bits. Do you agree?"

(Ada compiler) "For the target platform that you selected, I can confirm that this is indeed the case."

Depending on the target platform, however, the conversation might play out like this:

(Developer) "I think that `UInt_7_S32` should be stored using at least 32 bits. Do you agree?"

(Ada compiler) "For the target platform that you selected, I cannot possibly do it! COMPILATION ERROR!"

#### Component size

Let's continue our discussion on sizes with an example that makes use of the `Component_Size` attribute:

```

package Custom_Types is

type UInt_7 is range 0 .. 127;

type UInt_7_Array is
array (Positive range <>) of UInt_7;

type UInt_7_Array_Comp_32 is
array (Positive range <>) of UInt_7
with Component_Size => 32;

end Custom_Types;

with Custom_Types; use Custom_Types;

procedure Show_Sizes is
Arr_1 : UInt_7_Array (1 .. 20);
Arr_2 : UInt_7_Array_Comp_32 (1 .. 20);
begin
Put_Line
("UInt_7_Array'Size:                   "
& UInt_7_Array'Size'Image);
Put_Line
("UInt_7_Array'Object_Size:            "
& UInt_7_Array'Object_Size'Image);
Put_Line
("UInt_7_Array'Component_Size:         "
& UInt_7_Array'Component_Size'Image);
Put_Line
("Arr_1'Component_Size:                "
& Arr_1'Component_Size'Image);
Put_Line
("Arr_1'Size:                          "
& Arr_1'Size'Image);
New_Line;

Put_Line
("UInt_7_Array_Comp_32'Object_Size:    "
& UInt_7_Array_Comp_32'Size'Image);
Put_Line
("UInt_7_Array_Comp_32'Object_Size:    "
& UInt_7_Array_Comp_32'Object_Size'Image);
Put_Line
("UInt_7_Array_Comp_32'Component_Size: "
&
UInt_7_Array_Comp_32'Component_Size'Image);
Put_Line
("Arr_2'Component_Size:                "
& Arr_2'Component_Size'Image);
Put_Line
("Arr_2'Size:                          "
& Arr_2'Size'Image);
New_Line;
end Show_Sizes;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Depending on your target architecture, you may see this output:

```UInt_7_Array'Size:                    17179869176
UInt_7_Array'Object_Size:             17179869176
UInt_7_Array'Component_Size:          8
Arr_1'Component_Size:                 8
Arr_1'Size:                           160

UInt_7_Array_Comp_32'Size:            68719476704
UInt_7_Array_Comp_32'Object_Size:     68719476704
UInt_7_Array_Comp_32'Component_Size:  32
Arr_2'Component_Size:                 32
Arr_2'Size:                           640
```

Here, the value we get for `Component_Size` of the `UInt_7_Array` type is 8 bits, which matches the `UInt_7'Object_Size` — as we've seen in the previous subsection. In general, we expect the component size to match the object size of the underlying type.

However, we might have component sizes that aren't equal to the object size of the component's type. For example, in the declaration of the `UInt_7_Array_Comp_32` type, we're using the `Component_Size` aspect to query whether the size of each component can be 32 bits:

```type UInt_7_Array_Comp_32 is
array (Positive range <>) of UInt_7
with Component_Size => 32;
```

If the code compiles, we see this value when we use the `Component_Size` attribute. In this case, even though `UInt_7'Object_Size` is 8 bits, the component size of the array type (`UInt_7_Array_Comp_32'Component_Size`) is 32 bits.

Note that we can use the `Component_Size` attribute with data types, as well as with actual objects of that data type. Therefore, we can write `UInt_7_Array'Component_Size` and `Arr_1'Component_Size`, for example.

This big number (17179869176 bits) for `UInt_7_Array'Size` and `UInt_7_Array'Object_Size` might be surprising for you. This is due to the fact that Ada is reporting the size of the `UInt_7_Array` type for the case when the complete range is used. Considering that we specified a positive range in the declaration of the `UInt_7_Array` type, the maximum length on this machine is 231 - 1. The object size of an array type is calculated by multiplying the maximum length by the component size. Therefore, the object size of the `UInt_7_Array` type corresponds to the multiplication of 231 - 1 components (maximum length) by 8 bits (component size).

#### Storage size

To complete our discussion on sizes, let's look at this example of storage sizes:

```

package Custom_Types is

type UInt_7 is range 0 .. 127;

type UInt_7_Access is access UInt_7;

end Custom_Types;

with System;

with Custom_Types; use Custom_Types;

procedure Show_Sizes is
AV1, AV2 : UInt_7_Access;
begin
Put_Line
("UInt_7_Access'Storage_Size:          "
& UInt_7_Access'Storage_Size'Image);
Put_Line
("UInt_7_Access'Storage_Size (bits):   "
& Integer'Image (UInt_7_Access'Storage_Size
* System.Storage_Unit));

Put_Line
("UInt_7'Size:               "
& UInt_7'Size'Image);
Put_Line
("UInt_7_Access'Size:        "
& UInt_7_Access'Size'Image);
Put_Line
("UInt_7_Access'Object_Size: "
& UInt_7_Access'Object_Size'Image);
Put_Line
("AV1'Size:                  "
& AV1'Size'Image);
New_Line;

Put_Line ("Allocating AV1...");
AV1 := new UInt_7;
Put_Line ("Allocating AV2...");
AV2 := new UInt_7;
New_Line;

Put_Line
("AV1.all'Size:              "
& AV1.all'Size'Image);
New_Line;
end Show_Sizes;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Depending on your target architecture, you may see this output:

```UInt_7_Access'Storage_Size:           0
UInt_7_Access'Storage_Size (bits):    0

UInt_7'Size:                7
UInt_7_Access'Size:         64
UInt_7_Access'Object_Size:  64
AV1'Size:                   64

Allocating AV1...
Allocating AV2...

AV1.all'Size:               8
```

As we've mentioned earlier on, `Storage_Size` corresponds to the number of storage elements reserved for an access type or a task object. In this case, we see that the storage size of the `UInt_7_Access` type is zero. This is because we haven't indicated that memory should be reserved for this data type. Thus, the compiler doesn't reserve memory and simply sets the size to zero.

Because `Storage_Size` gives us the number of storage elements, we have to multiply this value by `System.Storage_Unit` — which gives us the size (in bits) of a single storage element — to get the total storage size in bits. (In this particular example, however, the multiplication doesn't make any difference, as the number of storage elements is zero.)

Note that the size of our original data type `UInt_7` is 7 bits, while the size of its corresponding access type `UInt_7_Access` (and the access object `AV1`) is 64 bits. This is due to the fact that the access type doesn't contain an object, but rather memory information about an object. You can retrieve the size of an object allocated via `new` by first dereferencing it — in our example, we do this by writing `AV1.all'Size`.

Now, let's use the `Storage_Size` aspect to actually reserve memory for this data type:

```

package Custom_Types is

type UInt_7 is range 0 .. 127;

type UInt_7_Reserved_Access is access UInt_7
with Storage_Size => 8;

end Custom_Types;

with System;

with Custom_Types; use Custom_Types;

procedure Show_Sizes is
RAV1, RAV2 : UInt_7_Reserved_Access;
begin
Put_Line
("UInt_7_Reserved_Access'Storage_Size:        "
& UInt_7_Reserved_Access'Storage_Size'Image);

Put_Line
("UInt_7_Reserved_Access'Storage_Size (bits): "
& Integer'Image
(UInt_7_Reserved_Access'Storage_Size
* System.Storage_Unit));

Put_Line
("UInt_7_Reserved_Access'Size:        "
& UInt_7_Reserved_Access'Size'Image);
Put_Line
("UInt_7_Reserved_Access'Object_Size: "
& UInt_7_Reserved_Access'Object_Size'Image);
Put_Line
("RAV1'Size:                          "
& RAV1'Size'Image);
New_Line;

Put_Line ("Allocating RAV1...");
RAV1 := new UInt_7;
Put_Line ("Allocating RAV2...");
RAV2 := new UInt_7;
New_Line;
end Show_Sizes;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Depending on your target architecture, you may see this output:

```UInt_7_Reserved_Access'Storage_Size:         8
UInt_7_Reserved_Access'Storage_Size (bits):  64

UInt_7_Reserved_Access'Size:         64
UInt_7_Reserved_Access'Object_Size:  64
RAV1'Size:                           64

Allocating RAV1...
Allocating RAV2...

raised STORAGE_ERROR : s-poosiz.adb:108 explicit raise
```

In this case, we're reserving 8 storage elements in the declaration of `UInt_7_Reserved_Access`.

```type UInt_7_Reserved_Access is access UInt_7
with Storage_Size => 8;
```

Since each storage unit corresponds to one byte (8 bits) in this architecture, we're reserving a maximum of 64 bits for the `UInt_7_Reserved_Access` type.

This example raises an exception at runtime — a storage error, to be more specific. This is because the maximum reserved size is 64 bits, and the size of a single access object is 64 bits as well. Therefore, after the first allocation, the reserved storage space is already consumed, so we cannot allocate a second access object.

This behavior might be quite limiting in many cases. However, for certain applications where memory is very constrained, this might be exactly what we want to see. For example, having an exception being raised when the allocated memory for this data type has reached its limit might allow the application to have enough memory to at least handle the exception gracefully.

### Alignment

For many algorithms, it's important to ensure that we're using the appropriate alignment. This can be done by using the `Alignment` attribute and the `Alignment` aspect. Let's look at this example:

```

package Custom_Types is

type UInt_7 is range 0 .. 127;

type Aligned_UInt_7 is new UInt_7
with Alignment => 4;

end Custom_Types;

with Custom_Types; use Custom_Types;

procedure Show_Alignment is
V         : constant UInt_7         := 0;
Aligned_V : constant Aligned_UInt_7 := 0;
begin
Put_Line
("UInt_7'Alignment:           "
& UInt_7'Alignment'Image);
Put_Line
("UInt_7'Size:                "
& UInt_7'Size'Image);
Put_Line
("UInt_7'Object_Size:         "
& UInt_7'Object_Size'Image);
Put_Line
("V'Alignment:                "
& V'Alignment'Image);
Put_Line
("V'Size:                     "
& V'Size'Image);
New_Line;

Put_Line
("Aligned_UInt_7'Alignment:   "
& Aligned_UInt_7'Alignment'Image);
Put_Line
("Aligned_UInt_7'Size:        "
& Aligned_UInt_7'Size'Image);
Put_Line
("Aligned_UInt_7'Object_Size: "
& Aligned_UInt_7'Object_Size'Image);
Put_Line
("Aligned_V'Alignment:        "
& Aligned_V'Alignment'Image);
Put_Line
("Aligned_V'Size:             "
& Aligned_V'Size'Image);
New_Line;
end Show_Alignment;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Depending on your target architecture, you may see this output:

```UInt_7'Alignment:            1
UInt_7'Size:                 7
UInt_7'Object_Size:          8
V'Alignment:                 1
V'Size:                      8

Aligned_UInt_7'Alignment:    4
Aligned_UInt_7'Size:         7
Aligned_UInt_7'Object_Size:  32
Aligned_V'Alignment:         4
Aligned_V'Size:              32
```

In this example, we're reusing the `UInt_7` type that we've already been using in previous examples. Because we haven't specified any alignment for the `UInt_7` type, it has an alignment of 1 storage unit (or 8 bits). However, in the declaration of the `Aligned_UInt_7` type, we're using the `Alignment` aspect to request an alignment of 4 storage units (or 32 bits):

```type Aligned_UInt_7 is new UInt_7
with Alignment => 4;
```

When using the `Alignment` attribute for the `Aligned_UInt_7` type, we can confirm that its alignment is indeed 4 storage units (bytes).

Note that we can use the `Alignment` attribute for both data types and objects — in the code above, we're using `UInt_7'Alignment` and `V'Alignment`, for example.

Because of the alignment we're specifying for the `Aligned_UInt_7` type, its size — indicated by the `Object_Size` attribute — is 32 bits instead of 8 bits as for the `UInt_7` type.

Note that you can also retrieve the alignment associated with a class using `S'Class'Alignment`. For example:

```

procedure Show_Class_Alignment is

type Point_1D is tagged record
X : Integer;
end record;

type Point_2D is new Point_1D with record
Y : Integer;
end record
with Alignment => 16;

type Point_3D is new Point_2D with record
Z : Integer;
end record;

begin
Put_Line ("1D_Point'Alignment:       "
& Point_1D'Alignment'Image);
Put_Line ("1D_Point'Class'Alignment: "
& Point_1D'Class'Alignment'Image);
Put_Line ("2D_Point'Alignment:       "
& Point_2D'Alignment'Image);
Put_Line ("2D_Point'Class'Alignment: "
& Point_2D'Class'Alignment'Image);
Put_Line ("3D_Point'Alignment:       "
& Point_3D'Alignment'Image);
Put_Line ("3D_Point'Class'Alignment: "
& Point_3D'Class'Alignment'Image);
end Show_Class_Alignment;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

### Overlapping Storage

Algorithms can be designed to perform in-place or out-of-place processing. In other words, they can take advantage of the fact that input and output arrays share the same storage space or not.

We can use the `Has_Same_Storage` and the `Overlaps_Storage` attributes to retrieve more information about how the storage space of two objects related to each other:

• the `Has_Same_Storage` attribute indicates whether two objects have the exact same storage.

• A typical example is when both objects are exactly the same, so they obviously share the same storage. For example, for array `A`, `A'Has_Same_Storage (A)` is always `True`.

• the `Overlaps_Storage` attribute indicates whether two objects have at least one bit in common.

• Note that, if two objects have the same storage, this implies that their storage also overlaps. In other words, `A'Has_Same_Storage (B) = True` implies that `A'Overlaps_Storage (B) = True`.

Let's look at this example:

```

package Int_Array_Processing is

type Int_Array is
array (Positive range <>) of Integer;

procedure Show_Storage (X : Int_Array;
Y : Int_Array);

procedure Process (X :     Int_Array;
Y : out Int_Array);

end Int_Array_Processing;

package body Int_Array_Processing is

procedure Show_Storage (X : Int_Array;
Y : Int_Array) is
begin
if X'Has_Same_Storage (Y) then
Put_Line
("Info: X and Y have the same storage.");
else
Put_Line
("Info: X and Y don't have"
& "the same storage.");
end if;
if X'Overlaps_Storage (Y) then
Put_Line
("Info: X and Y overlap.");
else
Put_Line
("Info: X and Y don't overlap.");
end if;
end Show_Storage;

procedure Process (X :     Int_Array;
Y : out Int_Array) is
begin
Put_Line ("==== PROCESS ====");
Show_Storage (X, Y);

if X'Has_Same_Storage (Y) then
Put_Line ("In-place processing...");
else
if not X'Overlaps_Storage (Y) then
Put_Line
("Out-of-place processing...");
else
Put_Line
("Cannot process "
& "overlapping arrays...");
end if;
end if;
New_Line;
end Process;

end Int_Array_Processing;

with Int_Array_Processing;
use  Int_Array_Processing;

procedure Main is
A : Int_Array (1 .. 20) := (others => 3);
B : Int_Array (1 .. 20) := (others => 4);
begin
Process (A, A);
--  In-place processing:
--  sharing the exact same storage

Process (A (1 .. 10), A (10 .. 20));
--  Overlapping one component: A (10)

Process (A (1 .. 10), A (11 .. 20));
--  Out-of-place processing:
--  same array, but not sharing any storage

Process (A, B);
--  Out-of-place processing:
--  two different arrays
end Main;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In this code example, we implement two procedures:

• `Show_Storage`, which shows storage information about two arrays by using the `Has_Same_Storage` and `Overlaps_Storage` attributes.

• `Process`, which are supposed to process an input array `X` and store the processed data in the output array `Y`.

• Note that the implementation of this procedure is actually just a mock-up, so that no processing is actually taking place.

We have four different instances of how we can call the `Process` procedure:

• in the `Process (A, A)` call, we're using the same array for the input and output arrays. This is a perfect example of in-place processing. Because the input and the output arrays arguments are actually the same object, they obviously share the exact same storage.

• in the `Process (A (1 .. 10), A (10 .. 20))` call, we're using two slices of the `A` array as input and output arguments. In this case, a single component of the `A` array is shared: `A (10)`. Because the storage space is overlapping, but not exactly the same, neither in-place nor out-of-place processing can usually be used in this case.

• in the `Process (A (1 .. 10), A (11 .. 20))` call, even though we're using the same array `A` for the input and output arguments, we're using slices that are completely independent from each other, so that the input and output arrays are not sharing any storage in this case. Therefore, we can use out-of-place processing.

• in the `Process (A, B)` call, we have two different arrays — which obviously don't share any storage space —, so we can use out-of-place processing.

### Packed Representation

As we've seen previously, the minimum number of bits required to represent a data type might be less than the actual number of bits used to store an object of that same type. We've seen an example where `UInt_7'Size` was 7 bits, while `UInt_7'Object_Size` was 8 bits. The most extreme case is the one for the `Boolean` type: in this case, `Boolean'Size` is 1 bit, while `Boolean'Object_Size` might be 8 bits (or even more on certain architectures). In such cases, we have 7 (or more) unused bits in memory for each object of `Boolean` type. In other words, we're wasting memory. On the other hand, we're gaining speed of access because we can directly access each element without having to first change its internal representation back and forth. We'll come back to this point later.

The situation is even worse when implementing bit-fields, which can be declared as an array of `Boolean` components. For example:

```

package Flag_Definitions is

type Flags is
array (Positive range <>) of Boolean;

end Flag_Definitions;

with Flag_Definitions; use Flag_Definitions;

procedure Show_Flags is
Flags_1 : Flags (1 .. 8);
begin
Put_Line ("Boolean'Size:           "
& Boolean'Size'Image);
Put_Line ("Boolean'Object_Size:    "
& Boolean'Object_Size'Image);
Put_Line ("Flags_1'Size:           "
& Flags_1'Size'Image);
Put_Line ("Flags_1'Component_Size: "
& Flags_1'Component_Size'Image);
end Show_Flags;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Depending on your target architecture, you may see this output:

```Boolean'Size:            1
Boolean'Object_Size:     8
Flags_1'Size:            64
Flags_1'Component_Size:  8
```

In this example, we're declaring the `Flags` type as an array of `Boolean` components. As we can see in this case, although the size of the `Boolean` type is just 1 bit, an object of this type has a size of 8 bits. Consequently, each component of the `Flags` type has a size of 8 bits. Moreover, an array with 8 components of `Boolean` type — such as the `Flags_1` array — has a size of 64 bits.

Therefore, having a way to compact the representation — so that we can store multiple objects without wasting storage space — may help us improving memory usage. This is actually possible by using the `Pack` aspect. For example, we could extend the previous example and declare a `Packed_Flags` type that makes use of this aspect:

```

package Flag_Definitions is

type Flags is
array (Positive range <>) of Boolean;

type Packed_Flags is
array (Positive range <>) of Boolean
with Pack;

end Flag_Definitions;

with Flag_Definitions; use Flag_Definitions;

procedure Show_Packed_Flags is
Flags_1 : Flags (1 .. 8);
Flags_2 : Packed_Flags (1 .. 8);
begin
Put_Line ("Boolean'Size:           "
& Boolean'Size'Image);
Put_Line ("Boolean'Object_Size:    "
& Boolean'Object_Size'Image);
Put_Line ("Flags_1'Size:           "
& Flags_1'Size'Image);
Put_Line ("Flags_1'Component_Size: "
& Flags_1'Component_Size'Image);
Put_Line ("Flags_2'Size:           "
& Flags_2'Size'Image);
Put_Line ("Flags_2'Component_Size: "
& Flags_2'Component_Size'Image);
end Show_Packed_Flags;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Depending on your target architecture, you may see this output:

```Boolean'Size:            1
Boolean'Object_Size:     8
Flags_1'Size:            64
Flags_1'Component_Size:  8
Flags_2'Size:            8
Flags_2'Component_Size:  1
```

In this example, we're declaring the `Flags_2` array of `Packed_Flags` type. Its size is 8 bits — instead of the 64 bits required for the `Flags_1` array. Because the array type `Packed_Flags` is packed, we can now effectively use this type to store an object of `Boolean` type using just 1 bit of the memory, as indicated by the `Flags_2'Component_Size` attribute.

In many cases, we need to convert between a normal representation (such as the one used for the `Flags_1` array above) to a packed representation (such as the one for the `Flags_2` array). In many programming languages, this conversion may require writing custom code with manual bit-shifting and bit-masking to get the proper target representation. In Ada, however, we just need to indicate the actual type conversion, and the compiler takes care of generating code containing bit-shifting and bit-masking to performs the type conversion.

Let's modify the previous example and introduce this type conversion:

```

package Flag_Definitions is

type Flags is
array (Positive range <>) of Boolean;

type Packed_Flags is
array (Positive range <>) of Boolean
with Pack;

Default_Flags : constant Flags :=
(True, True, False, True,
False, False, True, True);

end Flag_Definitions;

with Flag_Definitions; use Flag_Definitions;

procedure Show_Flag_Conversion is
Flags_1 : Flags (1 .. 8);
Flags_2 : Packed_Flags (1 .. 8);
begin
Flags_1 := Default_Flags;
Flags_2 := Packed_Flags (Flags_1);

for I in Flags_2'Range loop
Put_Line (I'Image & ": "
& Flags_1 (I)'Image & ", "
& Flags_2 (I)'Image);
end loop;
end Show_Flag_Conversion;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In this extended example, we're now declaring `Default_Flags` as an array of constant flags, which we use to initialize `Flags_1`.

The actual conversion happens with `Flags_2 := Packed_Flags (Flags_1)`. Here, the type conversion `Packed_Flags()` indicates that we're converting from the normal representation (used for the `Flags` type) to the packed representation (used for `Packed_Flags` type). We don't need to write more code than that to perform the correct type conversion.

Also, by using the same strategy, we could read information from a packed representation. For example:

```Flags_1 := Flags (Flags_2);
```

In this case, we use `Flags()` to convert from a packed representation to the normal representation.

We elaborate on the topic of converting between data representations in the section on changing data representation.

As indicated previously, when we're using a packed representation (vs. using a standard unpacked representation), we're trading off speed of access for less memory consumption. The following table summarizes this:

Representation

More speed of access

Less memory consumption

Unpacked

X

Packed

X

On one hand, we have better memory usage when we apply packed representations because we may save many bits for each object. On the other hand, there's a cost associated with accessing those packed objects because they need to be unpacked before we can actually access them. In fact, the compiler generates code — using bit-shifting and bit-masking — that converts a packed representation into an unpacked representation, which we can then access. Also, when storing a packed object, the compiler generates code that converts the unpacked representation of the object into the packed representation.

This packing and unpacking mechanism has a performance cost associated with it, which results in less speed of access for packed objects. As usual in those circumstances, before using packed representation, we should assess whether memory constraints are more important than speed in our target architecture.

## Record Representation and storage clauses

In this section, we discuss how to use record representation clauses to specify how a record is represented in memory. Our goal is to provide a brief introduction into the topic. If you're interested in more details, you can find a thorough discussion about record representation clauses in the Introduction to Embedded Systems Programming course.

Let's start with the simple approach of declaring a record type without providing further information. In this case, we're basically asking the compiler to select a reasonable representation for that record in the memory of our target architecture.

Let's see a simple example:

```

package P is

type R is record
A : Integer;
B : Integer;
end record;

end P;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Considering a typical 64-bit PC architecture with 8-bit storage units, and `Integer` defined as a 32-bit type, we get this memory representation:

Each storage unit is a position in memory. In the graph above, the numbers on the top (0, 1, 2, ...) represent those positions for record `R`.

In addition, we can show the bits that are used for components `A` and `B`:

The memory representation we see in the graph above can be described in Ada using representation clauses, as you can see in the code starting at the `for R use record` line in the code example below — we'll discuss the syntax and further details right after this example.

```

package P is

type R is record
A : Integer;
B : Integer;
end record;

--  Representation clause for record R:
for R use record
A at 0 range 0 .. 31;
--   ^ starting memory position
B at 4 range 0 .. 31;
--           ^ first bit .. last bit
end record;

end P;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Here, we're specifying that the `A` component is stored in the bits #0 up to #31 starting at position #0. Note that the position itself doesn't represent an absolute address in the device's memory; instead, it's relative to the memory space reserved for that record. The `B` component has the same 32-bit range, but starts at position #4.

This is a generalized view of the syntax:

```for Record_Type use record
Component_Name at Start_Position
range First_Bit .. Last_Bit;
end record;
```

These are the elements we see above:

• `Component_Name`: name of the component (from the record type declaration);

• `Start_Position`: start position — in storage units — of the memory space reserved for that component;

• `First_Bit`: first bit (in the start position) of the component;

• `Last_Bit`: last bit of the component.

Note that the last bit of a component might be in a different storage unit. Since the `Integer` type has a larger width (32 bits) than the storage unit (8 bits), components of that type span over multiple storage units. Therefore, in our example, the first bit of component `A` is at position #0, while the last bit is at position #3.

Also note that the last eight bits of component `A` are bits #24 .. #31. If we think in terms of storage units, this corresponds to bits #0 .. #7 of position #3. However, when specifying the last bit in Ada, we always use the `First_Bit` value as a reference, not the position where those bits might end up. Therefore, we write `range 0 .. 31`, well knowing that those 32 bits span over four storage units (positions #0 .. #3).

### Storage Place Attributes

We can retrieve information about the start position, and the first and last bits of a component by using the storage place attributes:

• `Position`, which retrieves the start position of a component;

• `First_Bit`, which retrieves the first bit of a component;

• `Last_Bit`, which retrieves the last bit of a component.

Note, however, that these attributes can only be used with actual records, and not with record types.

We can revisit the previous example and verify how the compiler represents the `R` type in memory:

```

package P is

type R is record
A : Integer;
B : Integer;
end record;

end P;

with System;

with P;           use P;

procedure Show_Storage is
R1 : R;
begin
Put_Line ("R'Size:              "
& R'Size'Image);
Put_Line ("R'Object_Size:       "
& R'Object_Size'Image);
New_Line;

Put_Line ("System.Storage_Unit: "
& System.Storage_Unit'Image);
New_Line;

Put_Line ("R1.A'Position  : "
& R1.A'Position'Image);
Put_Line ("R1.A'First_Bit : "
& R1.A'First_Bit'Image);
Put_Line ("R1.A'Last_Bit  : "
& R1.A'Last_Bit'Image);
New_Line;

Put_Line ("R1.B'Position  : "
& R1.B'Position'Image);
Put_Line ("R1.B'First_Bit : "
& R1.B'First_Bit'Image);
Put_Line ("R1.B'Last_Bit  : "
& R1.B'Last_Bit'Image);
end Show_Storage;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

On a typical 64-bit PC architecture, you probably see this output:

```R'Size:               64
R'Object_Size:        64
System.Storage_Unit:  8

R1.A'Position  :  0
R1.A'First_Bit :  0
R1.A'Last_Bit  :  31

R1.B'Position  :  4
R1.B'First_Bit :  0
R1.B'Last_Bit  :  31
```

First of all, we see that the size of the `R` type is 64 bits, which can be explained by those two 32-bit integer components. Then, we see that components `A` and `B` start at positions #0 and #4, and each one makes use of bits in the range from #0 to #31. This matches the graph we've seen above.

### Using Representation Clauses

We can use representation clauses to change the way the compiler handles memory for a record type. For example, let's say we want to have an empty storage unit between components `A` and `B`. We can use a representation clause where we specify that component `B` starts at position #5 instead of #4, leaving an empty byte after component `A` and before component `B`:

This is the code that implements that:

```

package P is

type R is record
A : Integer;
B : Integer;
end record;

for R use record
A at 0 range 0 .. 31;
B at 5 range 0 .. 31;
end record;

end P;

with P;           use P;

procedure Show_Empty_Byte is
begin
Put_Line ("R'Size:        "
& R'Size'Image);
Put_Line ("R'Object_Size: "
& R'Object_Size'Image);
end Show_Empty_Byte;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

When running the application above, we see that, due to the extra byte in the record representation, the sizes increase. On a typical 64-bit PC, `R'Size` is now 76 bits, which reflects the additional eight bits that we introduced between components `A` and `B`. Depending on the target architecture, you may also see that `R'Object_Size` is now 96 bits, which is the size the compiler selects as the most appropriate for this record type. As we've mentioned in the previous section, we can use aspects to request a specific size to the compiler. In this case, we could use the `Object_Size` aspect:

```

package P is

type R is record
A : Integer;
B : Integer;
end record
with Object_Size => 72;

for R use record
A at 0 range 0 .. 31;
B at 5 range 0 .. 31;
end record;

end P;

with P;           use P;

procedure Show_Empty_Byte is
begin
Put_Line ("R'Size:        "
& R'Size'Image);
Put_Line ("R'Object_Size: "
& R'Object_Size'Image);
end Show_Empty_Byte;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

If the code compiles, `R'Size` and `R'Object_Size` should now have the same value.

### Derived Types And Representation Clauses

In some cases, you might want to modify the memory representation of a record without impacting existing code. For example, you might want to use a record type that was declared in a package that you're not allowed to change. Also, you would like to modify its memory representation in your application. A nice strategy is to derive a type and use a representation clause for the derived type.

We can apply this strategy on our previous example. Let's say we would like to use record type `R` from package `P` in our application, but we're not allowed to modify package `P` — or the record type, for that matter. In this case, we could simply derive `R` as `R_New` and use a representation clause for `R_New`. This is exactly what we do in the specification of the child package `P.Rep`:

```

package P is

type R is record
A : Integer;
B : Integer;
end record;

end P;

package P.Rep is

type R_New is new R
with Object_Size => 72;

for R_New use record
A at 0 range 0 .. 31;
B at 5 range 0 .. 31;
end record;

end P.Rep;

with P;           use P;
with P.Rep;       use P.Rep;

procedure Show_Empty_Byte is
begin
Put_Line ("R'Size:        "
& R'Size'Image);
Put_Line ("R'Object_Size: "
& R'Object_Size'Image);

Put_Line ("R_New'Size:        "
& R_New'Size'Image);
Put_Line ("R_New'Object_Size: "
& R_New'Object_Size'Image);
end Show_Empty_Byte;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

When running this example, we see that the `R` type retains the memory representation selected by the compiler for the target architecture, while the `R_New` has the memory representation that we specified.

### Representation on Bit Level

A very common application of representation clauses is to specify individual bits of a record. This is particularly useful, for example, when mapping registers or implementing protocols.

Let's consider the following fictitious register as an example:

Here, `S` is the current status, `Error` is a flag, and `V1` contains a value. Due to the fact that we can use representation clauses to describe individual bits of a register as records, the implementation becomes as simple as this:

```

package P is

Processing, Done);
type UInt_3 is range 0 .. 2 ** 3 - 1;

type Simple_Reg is record
S     : Status;
Error : Boolean;
V1    : UInt_3;
end record;

for Simple_Reg use record
S     at 0 range 0 .. 1;
--  Bit #2 and 3: reserved!
Error at 0 range 4 .. 4;
V1    at 0 range 5 .. 7;
end record;

end P;

with P;           use P;

procedure Show_Simple_Reg is
begin
Put_Line ("Simple_Reg'Size:        "
& Simple_Reg'Size'Image);
Put_Line ("Simple_Reg'Object_Size: "
& Simple_Reg'Object_Size'Image);
end Show_Simple_Reg;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As we can see in the declaration of the `Simple_Reg` type, each component represents a field from our register, and it has a fixed location (which matches the register representation we see in the graph above). Any operation on the register is as simple as accessing the record component. For example:

```

with P;           use P;

procedure Show_Simple_Reg is
Default : constant Simple_Reg :=
Error => False,
V1    => 0);

R : Simple_Reg := Default;
begin
Put_Line ("R.S:  " & R.S'Image);

R.V1 := 4;

Put_Line ("R.V1: " & R.V1'Image);
end Show_Simple_Reg;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As we can see in the example, to retrieve the current status of the register, we just have to write `R.S`. To update the V1 field of the register with the value 4, we just have to write `R.V1 := 4`. No extra code — such as bit-masking or bit-shifting — is needed here.

In other languages

Some programming languages require that developers use complicated, error-prone approaches — which may include manually bit-shifting and bit-masking variables — to retrieve information from or store information to individual bits or registers. In Ada, however, this is efficiently handled by the compiler, so that developers only need to correctly describe the register mapping using representation clauses.

## Changing Data Representation

Note

This section was originally written by Robert Dewar and published as Gem #27: Changing Data Representation and Gem #28.

A powerful feature of Ada is the ability to specify the exact data layout. This is particularly important when you have an external device or program that requires a very specific format. Some examples are:

```

package Communication is

type Com_Packet is record
Key : Boolean;
Id  : Character;
Val : Integer range 100 .. 227;
end record;

for Com_Packet use record
Key at 0 range 0 .. 0;
Id  at 0 range 1 .. 8;
Val at 0 range 9 .. 15;
end record;

end Communication;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

which lays out the fields of a record, and in the case of `Val`, forces a biased representation in which all zero bits represents 100. Another example is:

```

package Array_Representation is

type Val is (A, B, C, D, E, F, G, H);

type Arr is array (1 .. 16) of Val
with Component_Size => 3;

end Array_Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

which forces the components to take only 3 bits, crossing byte boundaries as needed. A final example is:

```

package Enumeration_Representation is

type Status is (Off, On, Unknown);
for Status use (Off     => 2#001#,
On      => 2#010#,
Unknown => 2#100#);

end Enumeration_Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

which allows specified values for an enumeration type, instead of the efficient default values of 0, 1, 2.

In all these cases, we might use these representation clauses to match external specifications, which can be very useful. The disadvantage of such layouts is that they are inefficient, and accessing individual components, or, in the case of the enumeration type, looping through the values can increase space and time requirements for the program code.

One approach that is often effective is to read or write the data in question in this specified form, but internally in the program represent the data in the normal default layout, allowing efficient access, and do all internal computations with this more efficient form.

To follow this approach, you will need to convert between the efficient format and the specified format. Ada provides a very convenient method for doing this, as described in RM 13.6 "Change of Representation".

The idea is to use type derivation, where one type has the specified format and the other has the normal default format. For instance for the array case above, we would write:

```

package Array_Representation is

type Val is (A, B, C, D, E, F, G, H);
type Arr is array (1 .. 16) of Val;

type External_Arr is new Arr
with Component_Size => 3;

end Array_Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Now we read and write the data using the `External_Arr` type. When we want to convert to the efficient form, `Arr`, we simply use a type conversion.

```

with Array_Representation;
use  Array_Representation;

procedure Using_Array_For_IO is
Input_Data  : External_Arr;
Work_Data   : Arr;
Output_Data : External_Arr;
begin

--  Now convert to internal form
Work_Data := Arr (Input_Data);

--  (computations using efficient
--   Work_Data form)

--  Convert back to external form
Output_Data := External_Arr (Work_Data);

end Using_Array_For_IO;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Using this approach, the quite complex task of copying all the data of the array from one form to another, with all the necessary masking and shift operations, is completely automatic.

Similar code can be used in the record and enumeration type cases. It is even possible to specify two different representations for the two types, and convert from one form to the other, as in:

```

package Enumeration_Representation is

type Status_In is (Off, On, Unknown);
type Status_Out is new Status_In;

for Status_In use (Off     => 2#001#,
On      => 2#010#,
Unknown => 2#100#);
for Status_Out use (Off     => 103,
On      => 1045,
Unknown => 7700);

end Enumeration_Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

There are two restrictions that must be kept in mind when using this feature. First, you have to use a derived type. You can't put representation clauses on subtypes, which means that the conversion must always be explicit. Second, there is a rule RM 13.1 (10) that restricts the placement of interesting representation clauses:

10 For an untagged derived type, no type-related representation items are allowed if the parent type is a by-reference type, or has any user-defined primitive subprograms.

All the representation clauses that are interesting from the point of view of change of representation are "type related", so for example, the following sequence would be illegal:

```

package Array_Representation is

type Val is (A, B, C, D, E, F, G, H);
type Arr is array (1 .. 16) of Val;

procedure Rearrange (Arg : in out Arr);

type External_Arr is new Arr
with Component_Size => 3;

end Array_Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Why these restrictions? Well, the answer is a little complex, and has to do with efficiency considerations, which we will address below.

### Restrictions

In the previous subsection, we discussed the use of derived types and representation clauses to achieve automatic change of representation. More accurately, this feature is not completely automatic, since it requires you to write an explicit conversion. In fact there is a principle behind the design here which says that a change of representation should never occur implicitly behind the back of the programmer without such an explicit request by means of a type conversion.

The reason for that is that the change of representation operation can be very expensive, since in general it can require component by component copying, changing the representation on each component.

Let's have a look at the `-gnatG` expanded code to see what is hidden under the covers here. For example, the conversion `Arr (Input_Data)` from the previous example generates the following expanded code:

```B26b : declare
[subtype p__TarrD1 is integer range 1 .. 16]
R25b : p__TarrD1 := 1;
begin
for L24b in 1 .. 16 loop
[subtype p__arr___XP3 is
system__unsigned_types__long_long_unsigned range 0 ..
16#FFFF_FFFF_FFFF#]
work_data := p__arr___XP3!((work_data and not shift_left!(
16#7#, 3 * (integer(L24b - 1)))) or shift_left!(p__arr___XP3!
(input_data (R25b)), 3 * (integer(L24b - 1))));
R25b := p__TarrD1'succ(R25b);
end loop;
end B26b;
```

That's pretty horrible! In fact, we could have simplified it for this section, but we have left it in its original form, so that you can see why it is nice to let the compiler generate all this stuff so you don't have to worry about it yourself.

Given that the conversion can be pretty inefficient, you don't want to convert backwards and forwards more than you have to, and the whole approach is only worthwhile if we'll be doing extensive computations involving the value.

The expense of the conversion explains two aspects of this feature that are not obvious. First, why do we require derived types instead of just allowing subtypes to have different representations, avoiding the need for an explicit conversion?

The answer is precisely that the conversions are expensive, and you don't want them happening behind your back. So if you write the explicit conversion, you get all the gobbledygook listed above, but you can be sure that this never happens unless you explicitly ask for it.

This also explains the restriction we mentioned in previous subsection from RM 13.1 (10):

10 For an untagged derived type, no type-related representation items are allowed if the parent type is a by-reference type, or has any user-defined primitive subprograms.

It turns out this restriction is all about avoiding implicit changes of representation. Let's have a look at how type derivation works when there are primitive subprograms defined at the point of derivation. Consider this example:

```

package My_Ints is

type My_Int_1 is range 1 .. 10;

function Odd (Arg : My_Int_1)
return Boolean;

type My_Int_2 is new My_Int_1;

end My_Ints;

package body My_Ints is

function Odd (Arg : My_Int_1)
return Boolean is
(True);
--  Dummy implementation!

end My_Ints;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Now when we do the type derivation, we inherit the function `Odd` for `My_Int_2`. But where does this function come from? We haven't written it explicitly, so the compiler somehow materializes this new implicit function. How does it do that?

We might think that a complete new function is created including a body in which `My_Int_2` replaces `My_Int_1`, but that would be impractical and expensive. The actual mechanism avoids the need to do this by use of implicit type conversions. Suppose after the above declarations, we write:

```

with My_Ints; use My_Ints;

procedure Using_My_Int is
Var : My_Int_2;
begin

if Odd (Var) then
--   ^ Calling Odd function
--     for My_Int_2 type.
null;
end if;

end Using_My_Int;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

The compiler translates this as:

```

with My_Ints; use My_Ints;

procedure Using_My_Int is
Var : My_Int_2;
begin

if Odd (My_Int_1 (Var)) then
--   ^ Converting My_Int_2 to
--     My_Int_1 type before
--     calling Odd function.
null;
end if;

end Using_My_Int;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

This implicit conversion is a nice trick, it means that we can get the effect of inheriting a new operation without actually having to create it. Furthermore, in a case like this, the type conversion generates no code, since `My_Int_1` and `My_Int_2` have the same representation.

But the whole point is that they might not have the same representation if one of them had a representation clause that made the representations different, and in this case the implicit conversion inserted by the compiler could be expensive, perhaps generating the junk we quoted above for the `Arr` case. Since we never want that to happen implicitly, there is a rule to prevent it.

The business of forbidding by-reference types (which includes all tagged types) is also driven by this consideration. If the representations are the same, it is fine to pass by reference, even in the presence of the conversion, but if there was a change of representation, it would force a copy, which would violate the by-reference requirement.

So to summarize this section, on the one hand Ada gives you a very convenient way to trigger these complex conversions between different representations. On the other hand, Ada guarantees that you never get these potentially expensive conversions happening unless you explicitly ask for them.

## Valid Attribute

When receiving data from external sources, we're subjected to problems such as transmission errors. If not handled properly, erroneous data can lead to major issues in an application.

One of those issues originates from the fact that transmission errors might lead to invalid information stored in memory. When proper checks are active, using invalid information is detected at runtime and an exception is raised at this point, which might then be handled by the application.

Instead of relying on exception handling, however, we could instead ensure that the information we're about to use is valid. We can do this by using the `Valid` attribute. For example, if we have a variable `Var`, we can verify that the value stored in `Var` is valid by writing `Var'Valid`, which returns a `Boolean` value. Therefore, if the value of `Var` isn't valid, `Var'Valid` returns `False`, so we can have code that handles this situation before we actually make use of `Var`. In other words, instead of handling a potential exception in other parts of the application, we can proactively verify that input information is correct and avoid that an exception is raised.

In the next example, we show an application that

• generates a file containing mock-up data, and then

• reads information from this file as state values.

The mock-up data includes valid and invalid states.

```

procedure Create_Test_File (File_Name : String);

procedure Create_Test_File (File_Name : String)
is
package Integer_Sequential_IO is new
use Integer_Sequential_IO;

F : File_Type;
begin
Create (F, Out_File, File_Name);
Write (F,  1);
Write (F,  2);
Write (F,  4);
Write (F,  3);
Write (F,  2);
Write (F,  10);
Close (F);
end Create_Test_File;

package States is

type State is (Off, On, Waiting)
with Size => Integer'Size;

for State use (Off     => 1,
On      => 2,
Waiting => 4);

package State_Sequential_IO is new

(File_Name : String);

end States;

package body States is

(File_Name : String)
is
use State_Sequential_IO;

F : State_Sequential_IO.File_Type;
S : State;

procedure Display_State (S : State) is
begin
--  Before displaying the value,
--  check whether it's valid or not.
if S'Valid then
Put_Line (S'Image);
else
Put_Line ("Invalid value detected!");
end if;
end Display_State;

begin
Open (F, In_File, File_Name);

while not End_Of_File (F) loop
Display_State (S);
end loop;

Close (F);

end States;

with States;           use States;
with Create_Test_File;

procedure Show_States_From_File is
File_Name : constant String := "data.bin";
begin
Create_Test_File (File_Name);
end Show_States_From_File;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

When running the application, you'd see this output:

```OFF
ON
WAITING
Invalid value detected!
ON
Invalid value detected!
```

Let's start our discussion on this example with the `States` package, which contains the declaration of the `State` type. This type is a simple enumeration containing three states: `Off`, `On` and `Waiting`. We're assigning specific integer values for this type by declaring an enumeration representation clause. Note that we're using the `Size` aspect to request that objects of this type have the same size as the `Integer` type. This becomes important later on when parsing data from the file.

In the `Create_Test_File` procedure, we create a file containing integer values, which is parsed later by the `Read_Display_States` procedure. The `Create_Test_File` procedure doesn't contain any reference to the `State` type, so we're not constrained to just writing information that is valid for this type. On the contrary, this procedure makes use of the `Integer` type, so we can write any integer value to the file. We use this strategy to write both valid and invalid values of `State` to the file. This allows us to simulate an environment where transmission errors occur.

We call the `Read_Display_States` procedure to read information from the file and display each state stored in the file. In the main loop of this procedure, we call `Read` to read a state from the file and store it in the `S` variable. We then call the nested `Display_State` procedure to display the actual state stored in `S`. The most important line of code in the `Display_State` procedure is the one that uses the `Valid` attribute:

```if S'Valid then
```

In this line, we're verifying that the `S` variable contains a valid state before displaying the actual information from `S`. If the value stored in `S` isn't valid, we can handle the issue accordingly. In this case, we're simply displaying a message indicating that an invalid value was detected. If we didn't have this check, the `Constraint_Error` exception would be raised when trying to use invalid data stored in `S` — this would happen, for example, after reading the integer value 3 from the input file.

In summary, using the `Valid` attribute is a good strategy we can employ when we know that information stored in memory might be corrupted.

## Unchecked Union

We've introduced variant records back in the Introduction to Ada course. In simple terms, a variant record is a record with discriminants that allows for changing its structure. Basically, it's a record containing a `case`.

The `State_Or_Integer` declaration in the `States` package below is an example of a variant record:

```

package States is

type State is (Off, On, Waiting)
with Size => Integer'Size;

for State use (Off     => 1,
On      => 2,
Waiting => 4);

type State_Or_Integer (Use_Enum : Boolean) is
record
case Use_Enum is
when False => I : Integer;
when True  => S : State;
end case;
end record;

procedure Display_State_Value
(V : State_Or_Integer);

end States;

package body States is

procedure Display_State_Value
(V : State_Or_Integer)
is
begin
Put_Line ("State: " & V.S'Image);
Put_Line ("Value: " & V.I'Image);
end Display_State_Value;

end States;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As mentioned in the previous course, if you try to access a component that is not valid for your record, a `Constraint_Error` exception is raised. For example, in the implementation of the `Display_State_Value` procedure, we're trying to retrieve the value of the integer component (`I`) of the `V` record. When calling this procedure, the `Constraint_Error` exception is raised as expected because `Use_Enum` is set to `True`, so that the `I` component is invalid — only the `S` component is valid in this case.

```

with States; use States;

procedure Show_Variant_Rec_Error is
V : State_Or_Integer (Use_Enum => True);
begin
V.S := On;
Display_State_Value (V);
end Show_Variant_Rec_Error;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In addition to not being able to read the value of a component that isn't valid, assigning a value to a component that isn't valid also raises an exception at runtime. In this example, we cannot assign to `V.I`:

```

with States; use States;

procedure Show_Variant_Rec_Error is
V : State_Or_Integer (Use_Enum => True);
begin
V.I := 4;
--  Error: V.I cannot be accessed because
--         Use_Enum is set to True.
end Show_Variant_Rec_Error;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

We may circumvent this limitation by using the `Unchecked_Union` aspect. For example, we can derive a new type from `State_Or_Integer` and use this aspect in its declaration. We do this in the declaration of the `Unchecked_State_Or_Integer` type below.

```

package States is

type State is (Off, On, Waiting)
with Size => Integer'Size;

for State use (Off     => 1,
On      => 2,
Waiting => 4);

type State_Or_Integer (Use_Enum : Boolean) is
record
case Use_Enum is
when False => I : Integer;
when True  => S : State;
end case;
end record;

type Unchecked_State_Or_Integer
(Use_Enum : Boolean) is new
State_Or_Integer (Use_Enum)
with Unchecked_Union;

procedure Display_State_Value
(V : Unchecked_State_Or_Integer);

end States;

package body States is

procedure Display_State_Value
(V : Unchecked_State_Or_Integer)
is
begin
Put_Line ("State: " & V.S'Image);
Put_Line ("Value: " & V.I'Image);
end Display_State_Value;

end States;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Because we now use the `Unchecked_State_Or_Integer` type for the input parameter of the `Display_State_Value` procedure, no exception is raised at runtime, as both components are now accessible. For example:

```

with States; use States;

procedure Show_Unchecked_Union is
V : State_Or_Integer (Use_Enum => True);
begin
V.S := On;
Display_State_Value
(Unchecked_State_Or_Integer (V));
end Show_Unchecked_Union;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Note that, in the call to the `Display_State_Value` procedure, we first need to convert the `V` argument from the `State_Or_Integer` to the `Unchecked_State_Or_Integer` type.

Also, we can assign to any of the components of a record that has the `Unchecked_Union` aspect. In our example, we can now assign to both the `S` and the `I` components of the `V` record:

```

with States; use States;

procedure Show_Unchecked_Union is
V : Unchecked_State_Or_Integer
(Use_Enum => True);
begin
V := (Use_Enum => True, S => On);
Display_State_Value (V);

V := (Use_Enum => False, I => 4);
Display_State_Value (V);
end Show_Unchecked_Union;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In the example above, we're use an aggregate in the assignments to `V`. By doing so, we avoid that `Use_Enum` is set to the wrong component. For example:

```

with States; use States;

procedure Show_Unchecked_Union is
V : Unchecked_State_Or_Integer
(Use_Enum => True);
begin
V.S := On;
Display_State_Value (V);

V.I := 4;
--  Error: cannot directly assign to V.I,
--         as Use_Enum is set to True.

Display_State_Value (V);
end Show_Unchecked_Union;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Here, even though the record has the `Unchecked_Union` attribute, we cannot directly assign to the `I` component because `Use_Enum` is set to `True`, so only the `S` is accessible. We can, however, read its value, as we do in the `Display_State_Value` procedure.

Be aware that, due to the fact the union is not checked, we might write invalid data to the record. In the example below, we initialize the `I` component with 3, which is a valid integer value, but results in an invalid value for the `S` component, as the value 3 cannot be mapped to the representation of the `State` type.

```

with States; use States;

procedure Show_Unchecked_Union is
V : Unchecked_State_Or_Integer
(Use_Enum => True);
begin
V := (Use_Enum => False, I => 3);
Display_State_Value (V);
end Show_Unchecked_Union;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

To mitigate this problem, we could use the `Valid` attribute — discussed in the previous section — for the `S` component before trying to use its value in the implementation of the `Display_State_Value` procedure:

```

package body States is

procedure Display_State_Value
(V : Unchecked_State_Or_Integer)
is
begin
if V.S'Valid then
Put_Line ("State: " & V.S'Image);
else
Put_Line ("State: <invalid>");
end if;
Put_Line ("Value: " & V.I'Image);
end Display_State_Value;

end States;

with States; use States;

procedure Show_Unchecked_Union is
V : Unchecked_State_Or_Integer
(Use_Enum => True);
begin
V := (Use_Enum => False, I => 3);
Display_State_Value (V);
end Show_Unchecked_Union;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

However, in general, you should avoid using the `Unchecked_Union` aspect due to the potential issues you might introduce into your application. In the majority of the cases, you don't need it at all — except for special cases such as when interfacing with C code that makes use of union types or solving very specific problems when doing low-level programming.

## Shared variable control

Ada has built-in support for handling both volatile and atomic data. Let's start by discussing volatile objects.

### Volatile

A volatile object can be described as an object in memory whose value may change between two consecutive memory accesses of a process A — even if process A itself hasn't changed the value. This situation may arise when an object in memory is being shared by multiple threads. For example, a thread B may modify the value of that object between two read accesses of a thread A. Another typical example is the one of memory-mapped I/O, where the hardware might be constantly changing the value of an object in memory.

Because the value of a volatile object may be constantly changing, a compiler cannot generate code to store the value of that object in a register and then use the value from the register in subsequent operations. Storing into a register is avoided because, if the value is stored there, it would be outdated if another process had changed the volatile object in the meantime. Instead, the compiler generates code in such a way that the process must read the value of the volatile object from memory for each access.

Let's look at a simple example:

```

procedure Show_Volatile_Object is
Val : Long_Float with Volatile;
begin
Val := 0.0;
for I in 0 .. 999 loop
Val := Val + 2.0 * Long_Float (I);
end loop;

Put_Line ("Val: " & Long_Float'Image (Val));
end Show_Volatile_Object;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In this example, `Val` has the `Volatile` aspect, which makes the object volatile. We can also use the `Volatile` aspect in type declarations. For example:

```

package Shared_Var_Types is

type Volatile_Long_Float is new
Long_Float with Volatile;

end Shared_Var_Types;

with Shared_Var_Types; use Shared_Var_Types;

procedure Show_Volatile_Type is
Val : Volatile_Long_Float;
begin
Val := 0.0;
for I in 0 .. 999 loop
Val := Val + 2.0 * Volatile_Long_Float (I);
end loop;

Put_Line ("Val: "
& Volatile_Long_Float'Image (Val));
end Show_Volatile_Type;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Here, we're declaring a new type `Volatile_Long_Float` in the `Shared_Var_Types` package. This type is based on the `Long_Float` type and uses the `Volatile` aspect. Any object of this type is automatically volatile.

In addition to that, we can declare components of an array to be volatile. In this case, we can use the `Volatile_Components` aspect in the array declaration. For example:

```

procedure Show_Volatile_Array_Components is
Arr : array (1 .. 2) of Long_Float
with Volatile_Components;
begin
Arr := (others => 0.0);

for I in 0 .. 999 loop
Arr (1) := Arr (1) +  2.0 * Long_Float (I);
Arr (2) := Arr (2) + 10.0 * Long_Float (I);
end loop;

Put_Line ("Arr (1): "
& Long_Float'Image (Arr (1)));
Put_Line ("Arr (2): "
& Long_Float'Image (Arr (2)));
end Show_Volatile_Array_Components;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Note that it's possible to use the `Volatile` aspect for the array declaration as well:

```

package Shared_Var_Types is

private
Arr : array (1 .. 2) of Long_Float
with Volatile;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Note that, if the `Volatile` aspect is specified for an object, then the `Volatile_Components` aspect is also specified automatically — if it makes sense in the context, of course. In the example above, even though `Volatile_Components` isn't specified in the declaration of the `Arr` array , it's automatically set as well.

### Independent

When you write code to access a single object in memory, you might actually be accessing multiple objects at once. For example, when you declare types that make use of representation clauses — as we've seen in previous sections —, you might be accessing multiple objects that are grouped together in a single storage unit. For example, if you have components `A` and `B` stored in the same storage unit, you cannot update `A` without actually writing (the same value) to `B`. Those objects aren't independently addressable because, in order to access one of them, we have to actually address multiple objects at once.

When an object is independently addressable, we call it an independent object. In this case, we make sure that, when accessing that object, we won't be simultaneously accessing another object. As a consequence, this feature limits the way objects can be represented in memory, as we'll see next.

To indicate that an object is independent, we use the `Independent` aspect:

```

package Shared_Var_Types is

I : Integer with Independent;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Similarly, we can use this aspect when declaring types:

```

package Shared_Var_Types is

type Independent_Boolean is new Boolean
with Independent;

type Flags is record
F1 : Independent_Boolean;
F2 : Independent_Boolean;
end record;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In this example, we're declaring the `Independent_Boolean` type and using it in the declaration of the `Flag` record type. Let's now derive the `Flags` type and use a representation clause for the derived type:

```

package Shared_Var_Types.Representation is

type Rep_Flags is new Flags;

for Rep_Flags use record
F1 at 0 range 0 .. 0;
F2 at 0 range 1 .. 1;
--            ^  ERROR: start position of
--                      F2 is wrong!
--    ^          ERROR: F1 and F2 share the
--                      same storage unit!
end record;

end Shared_Var_Types.Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As you can see when trying to compile this example, the representation clause that we used for `Rep_Flags` isn't following these limitations:

1. The size of each independent component must be a multiple of a storage unit.

2. The start position of each independent component must be a multiple of a storage unit.

For example, for architectures that have a storage unit of one byte — such as standard desktop computers —, this means that the size and the position of independent components must be a multiple of a byte. Let's correct the issues in the code above by:

• setting the size of each independent component to correspond to `Storage_Unit` — using a range between 0 and `Storage_Unit - 1` —, and

• setting the start position to zero.

This is the corrected version:

```

with System;

package Shared_Var_Types.Representation is

type Rep_Flags is new Flags;

for Rep_Flags use record
F1 at 0 range 0 .. System.Storage_Unit - 1;
F2 at 1 range 0 .. System.Storage_Unit - 1;
end record;

end Shared_Var_Types.Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Note that the representation that we're now using for `Rep_Flags` is most likely the representation that the compiler would have chosen for this data type. We could, however, have added an empty storage unit between `F1` and `F2` — by simply writing `F2 at 2 ...`:

```

with System;

package Shared_Var_Types.Representation is

type Rep_Flags is new Flags;

for Rep_Flags use record
F1 at 0 range 0 .. System.Storage_Unit - 1;
F2 at 2 range 0 .. System.Storage_Unit - 1;
end record;

end Shared_Var_Types.Representation;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As long as we follow the rules for independent objects, we're still allowed to use representation clauses that don't correspond to the one that the compiler might select.

For arrays, we can use the `Independent_Components` aspect:

```

package Shared_Var_Types is

Flags : array (1 .. 8) of Boolean
with Independent_Components;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

We've just seen in a previous example that some representation clauses might not work with objects and types that have the `Independent` aspect. The same restrictions apply when we use the `Independent_Components` aspect. For example, this aspect prevents that array components are packed when the `Pack` aspect is used. Let's discuss the following erroneous code example:

```

package Shared_Var_Types is

type Flags is
array (Positive range <>) of Boolean
with Independent_Components, Pack;

F : Flags (1 .. 8) with Size => 8;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As expected, this code doesn't compile. Here, we can have either independent components, or packed components. We cannot have both at the same time because packed components aren't independently addressable. The compiler warns us that the `Pack` aspect won't have any effect on independent components. When we use the `Size` aspect in the declaration of `F`, we confirm this limitation. If we remove the `Size` aspect, however, the code is compiled successfully because the compiler ignores the `Pack` aspect and allocates a larger size for `F`:

```

package Shared_Var_Types is

type Flags is
array (Positive range <>) of Boolean
with Independent_Components, Pack;

end Shared_Var_Types;

with System;

with Shared_Var_Types; use Shared_Var_Types;

procedure Show_Flags_Size is
F : Flags (1 .. 8);
begin
Put_Line ("Flags'Size:      "
& F'Size'Image & " bits");
Put_Line ("Flags (1)'Size:  "
& F (1)'Size'Image & " bits");
Put_Line ("# storage units: "
& Integer'Image
(F'Size /
System.Storage_Unit));
end Show_Flags_Size;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As you can see in the output of the application, even though we specify the `Pack` aspect for the `Flags` type, the compiler allocates eight storage units, one per each component of the `F` array.

### Atomic

An atomic object is an object that only accepts atomic reads and updates. The Ada standard specifies that "for an atomic object (including an atomic component), all reads and updates of the object as a whole are indivisible." In this case, the compiler must generate Assembly code in such a way that reads and updates of an atomic object must be done in a single instruction, so that no other instruction could execute on that same object before the read or update completes.

In other contexts

Generally, we can say that operations are said to be atomic when they can be completed without interruptions. This is an important requirement when we're performing operations on objects in memory that are shared between multiple processes.

This definition of atomicity above is used, for example, when implementing databases. However, for this section, we're using the term "atomic" differently. Here, it really means that reads and updates must be performed with a single Assembly instruction.

For example, if we have a 32-bit object composed of four 8-bit bytes, the compiler cannot generate code to read or update the object using four 8-bit store / load instructions, or even two 16-bit store / load instructions. In this case, in order to maintain atomicity, the compiler must generate code using one 32-bit store / load instruction.

Because of this strict definition, we might have objects for which the `Atomic` aspect cannot be specified. Lots of machines support integer types that are larger than the native word-sized integer. For example, a 16-bit machine probably supports both 16-bit and 32-bit integers, but only 16-bit integer objects can be marked as atomic — or, more generally, only objects that fit into at most 16 bits.

Atomicity may be important, for example, when dealing with shared hardware registers. In fact, for certain architectures, the hardware may require that memory-mapped registers are handled atomically. In Ada, we can use the `Atomic` aspect to indicate that an object is atomic. This is how we can use the aspect to declare a shared hardware register:

```

with System;

package Shared_Var_Types is

private
R : Integer
with Atomic,

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Note that the `Address` aspect allows for assigning a variable to a specific location in the memory. In this example, we're using this aspect to specify the address of the memory-mapped register.

In addition to atomic objects, we can declare atomic types — similar to what we've seen before for volatile objects. For example:

```

with System;

package Shared_Var_Types is

type Atomic_Integer is new Integer
with Atomic;

private
R : Atomic_Integer

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In this example, we're declaring the `Atomic_Integer` type, which is an atomic type. Objects of this type — such as `R` in this example — are automatically atomic.

We can also declare atomic array components:

```

package Shared_Var_Types is

private
Arr : array (1 .. 2) of Integer
with Atomic_Components;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

This example shows the declaration of the `Arr` array, which has atomic components — the atomicity of its components is indicated by the `Atomic_Components` aspect.

Note that if an object is atomic, it is also volatile and independent. In other words, these type declarations are equivalent:

```

package Shared_Var_Types is

type Atomic_Integer_1 is new Integer
with Atomic;

type Atomic_Integer_2 is new Integer
with Atomic,
Volatile,
Independent;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

A simular rule applies to components of an array. When we use the `Atomic_Components`, the following aspects are implied: `Volatile`, `Volatile_Components` and `Independent_Components`. For example, these array declarations are equivalent:

```

package Shared_Var_Types is

Arr_1 : array (1 .. 2) of Integer
with Atomic_Components;

Arr_2 : array (1 .. 2) of Integer
with Atomic_Components,
Volatile,
Volatile_Components,
Independent_Components;

end Shared_Var_Types;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

As we know, we can use the `Image` attribute of a type to get a string associated with this type. This is useful for example when we want to display a user message for an enumeration type:

```

procedure Show_Enumeration_Image is

type Months is
(January, February, March, April,
May, June, July, August, September,
October, November, December);

M : constant Months := January;
begin
Put_Line ("Month: "
& Months'Image (M));
end Show_Enumeration_Image;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

This is similar to having this code:

```

procedure Show_Enumeration_Image is

type Months is
(January, February, March, April,
May, June, July, August, September,
October, November, December);

M : constant Months := January;

function Months_Image (M : Months)
return String is
begin
case M is
when January   => return "JANUARY";
when February  => return "FEBRUARY";
when March     => return "MARCH";
when April     => return "APRIL";
when May       => return "MAY";
when June      => return "JUNE";
when July      => return "JULY";
when August    => return "AUGUST";
when September => return "SEPTEMBER";
when October   => return "OCTOBER";
when November  => return "NOVEMBER";
when December  => return "DECEMBER";
end case;
end Months_Image;

begin
Put_Line ("Month: "
& Months_Image (M));
end Show_Enumeration_Image;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

Here, the `Months_Image` function associates a string with each month of the `Months` enumeration. As expected, the compiler needs to store the strings used in the `Months_Image` function when compiling this code. Similarly, the compiler needs to store strings for the `Months` enumeration for the `Image` attribute.

Sometimes, we don't need to call the `Image` attribute for a type. In this case, we could save some storage by eliminating the strings associated with the type. Here, we can use the `Discard_Names` aspect to request the compiler to reduce — as much as possible — the amount of storage used for storing names for this type. Let's see an example:

```

pragma Warnings (Off, "is not referenced");

type Months is
(January, February, March, April,
May, June, July, August, September,
October, November, December)

M : constant Months := January;
begin
null;

Enable tabbed editor view for this editor

Use the dark theme

-g

-O0

-gnata

-gnatW8

-gnatwa

-gnatyg0-s

-gnatyM50

-gnatyM80

-gnato

-gnato0

-gnato11

-gnato21

-gnato22

-gnato23

-gnateE

-gnatX

```

In this example, the compiler attempts to not store strings associated with the `Months` type duration compilation.

Note that the `Discard_Names` aspect is available for enumerations, exceptions, and tagged types.

In the GNAT toolchain

If we add this statement to the `Show_Discard_Names` procedure above:

```Put_Line ("Month: "
& Months'Image (M));
```

we see that the application displays "0" instead of "JANUARY". This is because GNAT doesn't store the strings associated with the `Months` type when we use the `Discard_Names` aspect for the `Months` type. (Therefore, the `Months'Image` attribute doesn't have that information.) Instead, the compiler uses the integer value of the enumeration, so that `Months'Image` returns the corresponding string for this integer value.