可変長配列(VLA)
C89の配列では、サイズはコンパイル時に定数リテラルである必要がありましたが、C99から、配列のサイズ(要素数)をプログラムの実行時に決定できるようになりました。これを可変長配列(Variable-Length Array)といいます。C89の配列では、サイズはコンパイル時に定数リテラルである必要がありました。可変長配列は、便利な機能だと思いますが、C11からオプション扱いになります。
『Cクイックリファレンス』124頁
インスタンス化ごとにサイズを決定
配列のサイズをインスタンス化ごとに決定できます、とかっこよく書きましたが、ようするに、int array[var]
と、配列の宣言時のサイズを変数で指定できます。次のコードでいえば、setArraySize()
関数が呼び出される度に配列のサイズの決定され、関数の終了と共に消滅します。
#include <stdio.h>
static void setArraySize(int size);
int main(void)
{
const int Size = 8;
for (int i = 1; i <= Size; i++) {
setArraySize(i);
}
return 0;
}
static void setArraySize(int size)
{
int array[size];
const int arraySize = sizeof array;
printf("size: %d byte\n", arraySize);
}
$ ./a.out
size: 4 byte
size: 8 byte
size: 12 byte
size: 16 byte
size: 20 byte
size: 24 byte
size: 28 byte
size: 32 byte
sizeof
演算子も実行時に計算されて結果がでています。
自動領域でのみ使える
可変長配列は、自動領域(スタック領域)に置かれた場合のみ扱えます。静的領域に置かれた場合はコンパイルエラーになります。(自動領域/静的領域の決定は、言語仕様ではなく実装次第かもしれません。)
グローバルでの宣言は、アドレスが静的領域に置かれます。
#include <stdio.h>
static const int Size = 32;
int array[Size];
int main(void)
{
printf("%d\n", sizeof array);
return 0;
}
$ gcc -pedantic-errors -std=c99 vla.c
vla.c:4:5: error: variably modified 'array' at file scope
4 | int array[Size];
| ^~~~~
static
指定子が付けられた場合、当然アドレスも静的領域に確保されます。
#include <stdio.h>
int main(void)
{
const int Size = 32;
static int array[Size];
printf("%d\n", sizeof array);
return 0;
}
$ gcc -pedantic-errors -std=c99 vla.c
vla.c: In function 'main':
vla.c:6:14: error: storage size of 'array' isn't constant
6 | static int array[Size];
| ^~~~~
マクロの代わりに使う
配列のサイズに変数が使えるようになったことで、マクロの代わりにconst
修飾子をつけた変数を使うことができます。
#include <stdio.h>
//#define int SIZE 8
//enum {
// SIZE = 8,
//};
static const int SIZE = 8;
static void sub(void);
int main(void)
{
char array[SIZE * 2];
printf("%s() array: %d byte\n", __func__, sizeof array);
sub();
return 0;
}
static void sub(void)
{
char array[SIZE / 2];
printf("%s() array: %d byte\n", __func__, sizeof array);
}
$ ./a.out
main() array: 16 byte
sub() array: 4 byte
ただし、変数ですから領域を持ちます。領域を持ちたくない場合は、enum
を使うことになると思います。
可変長配列の限界
一見便利な可変長配列ですが、自動領域に置かれるため、大きな配列を作ることができません。つぎのコードでは、9.5Mのサイズを確保しようとしてアクセスエラーになっています。そんなこともあって、C11ではオプションに格下げになったのかも知れません。
#include <stdio.h>
int main(void)
{
size_t Size = 10000000; //9.5M
int array[Size];
printf("%d\n", sizeof array);
return 0;
}
$ ./a.out
Segmentation fault: 11
大きな配列は、malloc()
やcalloc()
などを使う必要があります。例ではcalloc()
を使ってメモリを確保し、10000000
の要素に7
を代入しています。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
size_t Size = 10000001; //9.5M
//int *array = malloc(Size * sizeof(int));
int *array = calloc(Size, sizeof(int));
if (array == NULL) {
fprintf(stderr, "Allocation error\n");
}
array[Size-1] = 7;
printf("array[%8d]: %ld\n", 0, array[0]);
printf("array[%8d]: %ld\n", Size-2, array[Size-2]);
printf("array[%8d]: %ld\n", Size-1, array[Size-1]);
free(array);
return 0;
}
$ ./a.out
array[ 0]: 0
array[ 9999999]: 0
array[10000000]: 7
正しく確保され値が入っていることが分かります。