Ada 202x (42日目) - その他のaccessの変更

神は細部に宿るらしいです。 (意味分かんないで使ってる。)

access型と可視性

次のコードはコンパイラによってコンパイルできたりできなかったりしました。

with Ada.Unchecked_Deallocation;
package Pkg1 is
  type T (<>) is limited private; -- unconstrained and limited
  type T_Access is access T;
private
  type T is new String (1 .. 10); -- constrained and unlimited
  procedure Free is new Ada.Unchecked_Deallocation (T, T_Access); -- ?
end Pkg1;

公開部では T はunconstrainedかつlimitedの抽象型です。 T_Access は宣言時点ではそのような T へのaccess型です。 その後private部で T の実体はconstrainedかつunlimitedと明かされました。

このとき T_Access の示す先の型の可視性がどちらに従うかという話です。 宣言時点に従うならunconstrainedかつlimitedの抽象型、使用箇所に従うなら Pkg1 のprivate部やbody部ではconstrainedかつunlimitedの new String (1 .. 10) となります。

Ada.Unchecked_Deallocation は割り当てる型とそのaccess型を引数に取ります。 ところが T_Access の示す先が宣言時点のものですと Ada.Unchecked_Deallocation をインスタンス化している ? の行は T の実体と T_Access の示す先の抽象型が一致せずコンパイルエラーになります。

Ada 202xで有効なコードであることが確定しました。 コンパイルできるのが正しいです。

珍しくJanus/Adaが修正を迫られることになった件。 GNATとICC/Adaは元からこの挙動でした。

access型とfreezing

Adaには型のfreezingと呼ばれる概念があります。 型はその型の詳細が必要になった時点でfreezingされます。 freezingされた型はその後に内部表現を指定したりできなくなります。 (良い訳として何がいいでしょうね。)

declare
   type T is mod 2 ** 8;
   X : T;
   for T'Size use 8; -- error

この例では型 T に対してその型の変数 X を宣言した後から内部表現を指定しようとしておりエラーになります。

その上で細かい話になります。 access型の指す先のサブプログラムの引数や返値の型はfreezingされるような規格の文面になっていました。

declare
   type T is mod 2 ** 8;
   type A is access procedure (X : T);
   for T'Size use 8; -- error

勿論access型ですから宣言時点では型の詳細は必要ないです。

そして実際にはfreezingしないことを求めるようなACATSのテストがありました。

Ada 202xでfreezingしないと規格の文面が修正されました。 Adaを名乗るコンパイラはACATSのテストを通っていますので動作が変わるわけではないです。

匿名のaccess型とprotected

protected の中にサブプログラムを指す匿名のaccess型があった場合その匿名のaccess型まで protected になってしまっていました。

protected Prot1 is
   procedure High_Order (Callback : access procedure);
end Prot1;

このコードは次のコードと同じでした。

protected Prot2 is
   procedure High_Order (Callback : access protected procedure);
end Prot2;

Ada 202xで修正され、protected を付けていない場合は匿名のaccess型は普通のサブプログラムを指すようになりました。 protected に属するサブプログラムを指したい場合は protected を書く必要があります。

匿名のaccess型と呼び出し規約

record の中にサブプログラムを指す匿名のaccess型があった場合呼び出し規約は record に従うようになりました。

type R is
   record
      Callback : access procedure;
   end record
     with Convention => C;

この Callback の呼び出し規約は従来は Ada でした。 Ada 202xからは C になります。

便利になったのではあるのでしょうけれども。 呼び出し規約は細かく指定しましょう。

type R is
   record
      Callback : access procedure
        with Convention => Ada;
   end record
     with Convention => C;

匿名のaccess型の暗黙の型変換と生存期間

次の例を見てください。

declare
  type Integer_Access is access constant Integer;
  Global : Integer_Access;

  function Get_Access (X : aliased in Integer)
    return access constant Integer is
  begin
     return X'Access;
  end Get_Access;

begin
  declare
     Local : aliased Integer;
  begin
     Global := Get_Access (Local);
     Global := Integer_Access (Get_Access (Local)); -- error
  end;

関数の返値が匿名のaccess型の場合その寿命は引数から推定されます。 実際この例では Get_Access は単に引数のアクセス値を返しているだけです。

それを他のaccess型の変数に代入する場合は寿命のチェックが行われます。 ここで Integer_Access に明示的に型変換している行はエラーになるのですが、暗黙の型変換は見逃されていました。

Ada 202xで修正され、暗黙の型変換でもチェックが行われるようになりました。

access型のnot nullとtagged型のプリミティブ

tagged型のプリミティブは引数がaccess型の場合暗黙に not null ということになります。

package Pkg2 is
  type T is tagged null record;
  procedure Primitive (Object : access T);
end Pkg2;
-- bodyは省略

procedure Renamed (Object : not null access Pkg2.T) renames Pkg2.Primitive;

しかし private にしてしまいますと、公開部の情報では not null ではないということになります。

package Pkg3 is
   type T is private;
   procedure Primitive (Object : access T);
private
   type T is tagged null record;
end Pkg3;

Pkg3.Primitivenull を渡すと実行時 Constraint_Error になります。

ところが現在のGNATは可視性を無視して次の renames を許してしまいます。

procedure Renamed (Object : not null access Pkg3.T) renames Pkg3.Primitive;

Janus/Adaはこの renames をエラーにします。

またJanus/Adaはなんと本体側をもエラーにしてしまいます。

package body Pkg3 is

   procedure Primitive (Object : access T) is
   begin
      null;
   end Primitive;

end Pkg3;

ここでは可視性の上で Ttagged であると見えていますのでこの本体側の Primitivenot null 付きと見做されるようです。 そのため not null なしの仕様部と一致しなくなります。

この仕様がAda 202xで詰められました。 簡単に言えばJanus/Adaの勝利です。

Ada 95時代に書かれたコードは仕様部に not null を付けて回らないとエラーになります。 既にAda 95からAda 2005までの期間よりも、Ada 2005以降の方が長いんですよね……。

あー、未だにAda 95までの情報で書いてある記事滅びねえかなあ……。

access型のnot nullとrenamesとgeneric

次の例を見てください。

type Float_Access is access Float;

generic
   A : in Float_Access;
package Gen1 is
   procedure Proc1;
end Gen1;

package body Gen1 is

   procedure Proc1 is
      R : not null Float_Access renames A;
   begin
      null;
   end Proc1;

end Gen1;

R では not null を付けて generic の引数を renames しています。 勿論 Anull を渡すと実行時に Constraint_Error になります。

つまり気をつけないと危ないわけです。

in out の時は既に not null を一致させないとエラーになっていましたが in は違いました。 Ada 202xでは in も含めてコンパイル時エラーになります。

関連AI

  • AI12-0140-1 Access to unconstrained partial view when full view is constrained

  • AI12-0186-1 Profile freezing for the Access attribute

  • AI12-0207-1 Convention of anonymous access types

  • AI12-0278-1 Implicit conversions of anonymous return types

  • AI12-0287-1 Legality Rules for null exclusions in renaming are too fierce

  • AI12-0289-1 Implicitly null excluding anonymous access types and conformance