可変長配列
プログラミングにおいて、可変長配列(かへんちょうはいれつ、英語: variable-length array: VLA)とは、コンパイル時ではなく実行時に長さが決定される自動記憶期間の配列データ構造である[1]。
VLAに対応しているプログラミング言語には、Ada、ALGOL 68(行は固定)、APL、C99(ただしC11ではサポート必須ではなくオプションに格下げとなった[2][3]。VLAが文法として許されなかったC99以前のC言語では、alloca()
関数[4]あるいは類似の関数で同等の機能を実装することがあった)、C#(unsafeモードのスタック割り当て配列として)、COBOL、Fortran 90、J言語がある。また、Object Pascal(Borland Delphiで使用される言語)でも対応している。
メモリ
[編集]割り当て
[編集]C言語規格はスタック領域とヒープ領域の使い分けを規定しないが、多くの処理系では静的変数はスタック領域から、動的確保のメモリはヒープ領域から確保する[5]。VLAは動的に確保されるものだが、例えばGNU Cコンパイラのように、スタック上に確保する処理系も少なくない[6]。
C言語規格においては、静的配列と同様にsizeof演算子によってVLAのバイト容量を得られることが定められているため
[7]、VLAの上限はsizeof演算子の返り値の上限であるSIZE_MAX
バイトに制限されることになる[8]。
ただしこれは文法上の制限に過ぎず、VLAを必須とするC99に準拠する処理系であってもSIZE_MAXバイトまでのVLAを確保できる保証は無い。 ヒープ領域がギガバイトサイズの環境ではSIZE_MAXもギガバイト単位となることが多いので、ギガバイトサイズのVLAが文法上は許される。しかしそのような環境でも、個別の関数の実行にあたって用意されるスタック領域は数MiB程度の場合が多い[9]。VLAをスタック領域に確保する処理系であれば、巨大なVLAの確保はスタックオーバーフローを起こす。 malloc()のような動的メモリ確保はその失敗を戻り値によって知ることができるが、静的配列・VLAとも配列の確保の失敗はセグメント破壊のシグナル[10]などで検知するしかない。従って、単一スコープ内でmalloc()とfree()を繰り返すような処理であっても、それらを全てVLAで書き換えられるとは限らない。
変数アクセス
[編集]いくつかのプログラミング言語では、ポインタを介してVLAにアクセスすることができるが、不完全な型と見なされるため、デリファレンス(間接参照)されるとサイズを取得できなくなる[11]。
例
[編集]次のC99の関数は、指定されたサイズの可変長配列を割り当て、浮動小数点の値で埋めて、別の関数に渡す。配列は自動変数として宣言されているため、read_and_process()
から戻るとその存続期間が終了する。
float read_and_process(int n)
{
float vals[n];
for (int i = 0; i < n; i++)
vals[i] = read_val();
return process(n, vals);
}
C99では、関数呼び出しにおける長さのパラメータは、可変長配列のパラメータよりも前に来なければならない[1]。
以下はAdaにおける同じ例である。Adaの配列は境界を持っている。従って、長さをProcess関数に渡す必要はない。
type Vals_Type is array (Positive range <>) of Float;
function Read_And_Process (N : Integer) return Float is
Vals : Vals_Type (1 .. N);
begin
for I in 1 .. N loop
Vals (I) := Read_Val;
end loop;
return Process (Vals);
end Read_And_Process;
Fortran 90での同等の関数は次の通りである(コンパイル時にプロシージャ・インターフェイスを検査するFortran 90の機能を利用する場合)。
function read_and_process(n) result(o)
integer,intent(in)::n
real::o
real,dimension(n)::vals
integer::i
do i = 1,n
vals(i) = read_val()
end do
o = process(vals)
end function read_and_process
一方、関数がFortran 90以前の呼び出しインターフェイスを使用する場合は、まず(外部)関数を宣言し、配列の長さを引数として明示的に渡す必要がある(Cの場合と同様)。
function read_and_process(n) result(o)
integer,intent(in)::n
real::o
real,dimension(n)::vals
real::read_val, process
integer::i
do i = 1,n
vals(i) = read_val()
end do
o = process(vals,n)
end function read_and_process
次のCOBOLのコード断片は、PEOPLE-CNT
値で指定された長さ(メンバー数)を持つレコードの可変長配列DEPT-PERSON
を宣言する。
DATA DIVISION.
WORKING-STORAGE SECTION.
01 DEPT-PEOPLE.
05 PEOPLE-CNT PIC S9(4) BINARY.
05 DEPT-PERSON OCCURS 0 TO 20 TIMES DEPENDING ON PEOPLE-CNT.
10 PERSON-NAME PIC X(20).
10 PERSON-WAGE PIC S9(7)V99 PACKED-DECIMAL.
次のC#のコード断片は、整数型の可変長配列を宣言する。unsafe
というキーワードは、このコードを含むアセンブリが「安全でない」(unsafe) とマークされることを必要とする[注釈 1]。
unsafe void declareStackBasedArray(int size)
{
int *pArray = stackalloc int[size];
pArray[0] = 123;
}
動的割り当てと自動割り当て
[編集]Javaや.NET Frameworkなどの言語は、可変長配列を提供するとはみなされない。これらの言語では、全ての配列オブジェクトは論理的にヒープに割り当てられ、配列に関して自動記憶期間というものを持たないためである(JavaとdotNetのコンパイラは、これらのヒープ割り当てを可能な限りスタック上に実際に配置するよう最適化できる)。
脚注
[編集]注釈
[編集]- ^ より具体的にいうと、アセンブリのコンパイルオプションに "/unsafe" を追加する必要がある。
出典
[編集]- ^ a b “Variable Length Arrays”. 2017年9月21日閲覧。
- ^ “Variable Length - Using the GNU Compiler Collection (GCC)”. 2017年9月21日閲覧。
- ^ ISO 9899:2011 Programming Languages - C 6.7.6.2 4
- ^ スタック領域から動的にメモリを確保する関数。POSIX標準とはならなかったが、古くから様々なUnixシステムのCライブラリで提供されてきた。SunOS 4.1.3のman alloca、Ultrix 4.2のman alloca、2.10 BSDのman alloca、RedHat Linux 4.2のman alloca、Visual Studio 2015マニュアルの_alloca()などを参照。
- ^ 2つのメモリ領域の特性の違いはそれぞれの項を参照。関数やブロックスコープに閉じた変数には、ガベージコレクタが不要で確保・解放が高速なスタック領域を用いる言語が多い。
- ^ “Code Gen Options - The GNU Fortran Compiler”. 2017年9月21日閲覧。
- ^ ISO/IEC 9899:2011 §6.5.3.4 第2条参照。JTC1/SC22/WG14 N1570: ISO/IEC 9899:201x Committee Draft (2011-04-11) 2023年2月20日閲覧。これに対して、malloc()の戻り値ポインタにsizeof演算子を適用してもポインタ変数のバイト数しか得られない。
- ^ ISO/IEC 9899:2011 §7.20.3 第2条参照。
- ^ コールスタックの脚注3, 4を参照。
- ^ ISO/IEC 9899:2011 §7.14.1 参照。
- ^ “sizeof Operator”. 2017年9月21日閲覧。