Ada 202x (30日目) - 'Object_Size

たまにはGNAT以外のコンパイラからの逆輸入があってもいいと思います。

経緯

AdaではCPUに囚われず任意のビット数の型を定義できることはご存知かと思います。

例えば7ビットの符号なし整数型はこう定義します。

declare
   type Unsinged_7 is mod 2 ** 7;
   for Unsinged_7'Size use 7;

Ada 2012からのアスペクト構文であればこう書きます。

declare
   type Unsinged_7 is mod 2 ** 7
     with Size => 7;

しかしこの Unsigned_7 型の変数を配置したとしまして実際丁度7ビットだけが割り当てられるということはありません。 CPUが効率良くアクセスできるメモリの境界に合わせて切り上げられます。 8ビットが最小単位のCPUであれば実際には8ビット割り当てられることでしょう。 (アラインメントは割り当ての開始位置を調整することですのでこれは厳密にはアラインメントとはちょっと違う話でしょうか。)

中途半端なビット数の指定がそのまま使われるのは配列や record の中に入れてなおかつ Pack 等の内部表現の指定をした場合のみとなります。 配列や record の中であっても特に指定しない場合はやはり切り上げられます。

それで実際に割り当てられたビット数を知りたければ型ではなくてオブジェクトの 'Size 属性を参照できます。

declare
   type Unsigned_7_Array is array (Positive range <>) of Unsigned_7;
   type Packed_Unsigned_7_Array is array (Positive range <>) of Unsigned_7
     with Pack;
   Standalone : Unsigned_7;
   In_Array : Unsigned_7_Array (1 .. 1);
   In_Packed_Array : Packed_Unsigned_7_Array (1 .. 1);
begin
   Ada.Integer_IO.Put (Standalone'Size);          -- 8
   Ada.Integer_IO.Put (In_Array (1)'Size);        -- 8
   Ada.Integer_IO.Put (In_Packed_Array (1)'Size); -- 7

しかし場合によってはオブジェクトを介さずに切り上げ後のサイズを知りたいときもあります。

例えば内部表現を 'Size を参照して書きたい場合等です。

次の例は A をCPUにとって最適にアクセスできるよう配置して残る XYZ のみを詰め込むことを意図しています。 A が配置されるバイト(storage unit)を他の要素と共有しないことを明示するため aliased を付けています。

declare
   type Rec
      record
         A : aliased Unsinged_7;
         X, Y, Z : Boolean;
      end record;
   for Rec use
      record
         A at 0 range 0 .. Unsinged_7'Size - 1; -- error
         X at 0 range Unsinged_7'Size .. Unsinged_7'Size; -- error
         Y at 0 range Unsinged_7'Size + 1 .. Unsinged_7'Size + 1;
         Z at 0 range Unsinged_7'Size + 2 .. Unsinged_7'Size + 2;
      end record;
   for Rec'Alignment use Unsigned_7'Alignment;

これは上手く行きません。

Unsinged_7'Size は7ですので XY が1バイト目に詰め込まれてしまいます。 aliased を付けましたのでエラーとして検出できています。

このため従来は (Unsinged_7'Size + 7) / 8 * 8 等といった冗長なコードも書かれていました。

Ada 202xでの改善

'Object_Size

GNATの拡張だった 'Object_Size 属性が追認されています。 型の 'Object_Size 属性はその型で普通に宣言した変数の 'Size 属性と同じになります。

Unsinged_7'Object_Size であれば Standalone'Size と同じ8になります。

declare
   type Rec
      record
         A : aliased Unsinged_7;
         X, Y, Z : Boolean;
      end record;
   for Rec use
      record
         A at 0 range 0 .. Unsinged_7'Object_Size - 1;
         X at 0 range Unsinged_7'Object_Size .. Unsinged_7'Object_Size;
         Y at 0 range
           Unsinged_7'Object_Size + 1 .. Unsinged_7'Object_Size + 1;
         Z at 0 range
           Unsinged_7'Object_Size + 2 .. Unsinged_7'Object_Size + 2;
      end record;
   for Rec'Alignment use Unsigned_7'Alignment;

これで Rec の内部表現は A が0 .. 7ビット目(計8ビット)、X が8ビット目、Y が9ビット目、Z が10ビット目に配置されることになります。

Rec 自体は普通に変数を宣言すれば2バイトを占めるでしょう。 もし内部表現を書かなければ XYZ もそれぞれ1バイト使いますから4バイトになりますので圧縮成功です。

関連AI