Adaの定数の話

Cだと#defineするような、ハードコーディングしてメモリに直接乗せたくないような定数ってAdaでどうやって書くの? というお話。もっと言うと普遍整数型と数値宣言とconstって中でどうなってるのよという話。

まずはCで書いてみる。メモリに定数を乗っけるコード。

main() {
  int x = 17;
  int v = x;
  return v;
}

gcc -S -O0で最適化されていないアセンブラを吐いてみる。重要そうなところだけを抜き出すとこんな感じ。

main:
.LFB2:
	pushq	%rbp
.LCFI0:
	movq	%rsp, %rbp
.LCFI1:
	movl	$17, -8(%rbp)
	movl	-8(%rbp), %eax
	movl	%eax, -4(%rbp)
	movl	-4(%rbp), %eax
	leave
	ret

次に同じコードをAdaで書いてみる。

function Integer_Test_Normal return Integer is
   X : Integer := 17;
   V : Integer;
begin
   V := X;
   return V;
end Integer_Test_Normal;
_ada_integer_test_normal:
.LFB3:
	pushq	%rbp
.LCFI0:
	movq	%rsp, %rbp
.LCFI1:
	movl	$17, -8(%rbp)
	movl	$17, -4(%rbp)
	movl	$17, %eax
	leave
	ret

いきなり予想外の結果に。XVに17が直接代入されている。何でだろう。

ためしに、宣言部で初期化するのをやめてみる。

function Integer_Test_Normal return Integer is
   X : Integer;
   V : Integer;
begin
   X := 17;
   V := X;
   return V;
end Integer_Test_Normal;

しかし結果変わらず。フロントエンドがなんかしてるのかもしれない。まぁ変数が二つちゃんと出来てるのはわかるからいいや。

つぎに、17を#defineしてみる。

#define X 17
main() {
  int v = X;
  return v;
}

コンパイルするとこんな感じ。変数が1個になってる。

main:
.LFB2:
	pushq	%rbp
.LCFI0:
	movq	%rsp, %rbp
.LCFI1:
	movl	$17, -4(%rbp)
	movl	-4(%rbp), %eax
	leave
	ret

さて、Adaで同じように書くにはどうするべきか。とりあえず普遍整数で行ってみよう。

function Integer_Test_Universal return Integer is
   X : constant := 17;
   V : Integer;
begin
   V := X;
   return V;
end Integer_Test_Universal;
_ada_integer_test_universal:
.LFB3:
	pushq	%rbp
.LCFI0:
	movq	%rsp, %rbp
.LCFI1:
	movl	$17, -4(%rbp)
	movl	$17, %eax
	leave
	ret

お、良さそうな感じ(相変わらず代入してないのが気になるけど)。次は普通のconstantで実験。

function Integer_Test_Constant return Integer is
   X : constant Integer := 17;
   V : Integer;
begin
   V := X;
   return V;
end Integer_Test_Constant;
_ada_integer_test_constant:
.LFB3:
	pushq	%rbp
.LCFI0:
	movq	%rsp, %rbp
.LCFI1:
	movl	$17, -8(%rbp)
	movl	$17, -4(%rbp)
	movl	$17, %eax
	leave
	ret

予想通り、変数2つ。というのも、Adaのconstantは単に2回目の代入が出来ない変数なので、値自体は実行時に決まる事を考えればこうなるでしょう。

というわけで、#defineの代わりは普遍整数を使うのがいいようだ。しかし、整数以外の値を定数にしたい場合はどうしたものだろう。

ついでにもうちょっと実験。数値宣言した型のconstだとどうなるのかな。

function Integer_Test_Range return Integer is
   type X_Range is range 17 .. 17;
   X : constant X_Range := 17;
   V : Integer;
begin
   V := Integer(X);
   return V;
end Integer_Test_Range;
_ada_integer_test_range:
.LFB3:
	pushq	%rbp
.LCFI0:
	movq	%rsp, %rbp
.LCFI1:
	movb	$17, -5(%rbp)
	movl	$17, -4(%rbp)
	movl	$17, %eax
	leave
	ret

まぁ、そりゃそうか、という結果ですね。それにしても、アセンブリで見ると数値宣言がメモリ領域をちゃんと計算してくれているのがわかりますね。まぁ、そうなってないとIntegerすらろくに使えなくなっちゃうので当然ですが。というのも、Integerは計算機が計算しやすい大きさの数値宣言があらかじめ定義されてるだけなんですね。

俺は40bitの符号無し整数が欲しいんだよ、って時は適当に作れちゃいますよ。

procedure Integer_Test_Range40 is
   type Unsigned_Intger40 is range 0 .. 2 ** 40;
   X : constant Unsigned_Intger40 := 17;
begin
   null;
end Integer_Test_Range40;
_ada_integer_test_range40:
.LFB3:
	pushq	%rbp
.LCFI0:
	movq	%rsp, %rbp
.LCFI1:
	movq	$17, -8(%rbp)
	leave
	ret

内部的には64bitになってますね。内部表現説を使うにしてもprimitiveの場合2のpowerじゃないといけないので32bitの次は必然的に64bitになるわけですね。