作曲・指導・C言語・Linux

金沢音楽制作

金沢音楽制作では、楽曲・楽譜の制作と、作曲や写譜などレッスンを行っています。

可変長配列(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

正しく確保され値が入っていることが分かります。

更新情報