C言語基礎|配列の補足事項

ここまでで、配列の宣言・要素アクセス・初期化・走査・コピー・多次元配列まで一通り触れてきました。
でもC言語の配列には、「知っていると助かるけど、使いどころを間違えると危ない」補足トピックがいくつかあります。

この記事では、代表的な2つを紹介します。

  • 可変長配列(VLA:variable length array)
  • 要素指定子(designator:指示付き初期化)

どちらも便利に見える一方で、採用する前に知っておきたい落とし穴があります。ここを押さえると、配列の理解が一段深くなります。

可変長配列(VLA)って何?

ざっくり言うと

普通は配列の要素数は「定数」で決めますよね。
VLAは、要素数を 実行時に決まる値(変数) にできる配列です。

書式(宣言の形)

種類書式要素数が決まるタイミング
通常の配列型 変数名[定数式];コンパイル時
VLA型 変数名[式];実行時

例:関数引数 n に応じて配列サイズが決まる

void func(int n)
{
    int a[n];  // VLA:実行時にnが決まってから確保される
}

VLAのメリットと注意点(ここ大事)

メリット

メリットうれしい場面
動的にサイズを変えられる入力された件数だけ一時的に配列を作りたい。

注意点(実務での落とし穴)

注意点何が起きる?
基本的にスタック確保サイズが大きいとスタックオーバーフローしやすい
コンパイラがサポートしない場合がある移植性が落ちる(STDC_NO_VLA が定義されることがある)
サイズチェックを怠ると危険n が負や巨大値だと未定義動作やクラッシュ要因になる
Cの方言差が出やすいプロジェクトの方針によって禁止されがち

「学習としては分かりやすい」けど、常用はおすすめしにくいタイプの機能です。
実務では、サイズが動くなら malloc を使って明示的に管理したり、固定最大長+実際の利用数だけ使う、みたいな設計が好まれます。

サンプルプログラム(入力件数ぶんだけ一時配列を作って合計する)

今回は「購入した商品数ぶんの価格を入力して合計を出す」にプログラムを例に解説をします。

プロジェクト名:chap5-10-1 ソースファイル名:chap5-10-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

// 入力された件数ぶんだけ一時配列を確保して合計を出す(VLAの例)
#include <stdio.h>

int main(void)
{
    int n;

    printf("商品はいくつ買いましたか:");
    scanf("%d", &n);

    if (n < 1 || n > 50) {
        puts("入力できる個数は 1〜50 です。");
        return 0;
    }

    int price[n];     // VLA:実行時に n 個の配列を確保
    int sum = 0;

    puts("価格を入力してください。");
    for (int i = 0; i < n; i++) {
        printf("%d個目の価格:", i + 1);
        scanf("%d", &price[i]);
        sum += price[i];
    }

    printf("合計金額は %d 円です。\n", sum);
    return 0;
}

解説

このコードの読みどころ

変数役割
n実際の要素数(実行時に決定)
price[n]入力された価格を保持する配列
sum合計金額
  • n を読み取った後に price[n] を宣言しているのがポイント
  • つまり、n が決まる前に price[n] は作れません
  • サイズが大きくなりすぎないように、n の範囲チェックを入れています。

要素指定子(designator)とは?

ざっくり言うと

初期化子で「この要素番号にこの値を入れる」と 添字つきで指定できる機能です。

書式(配列の designated initializer)

書式意味
配列名 = { [添字] = 値, ... }指定した添字の要素に値を入れる

例:

int a[] = { [2] = 5, 9, [6] = 3, 1 };

この意味は「次の順で初期化する」です。

  1. a[2] = 5
  2. 次の要素 a[3] = 9
  3. a[6] = 3
  4. 次の要素 a[7] = 1

指定されなかった要素は 0 で初期化されます。

要素指定子のメリット(使いどころが分かりやすい)

使いどころ何が嬉しい?
テーブルの一部だけ値を入れたい指定してない部分は0になるので書く量が減る。
定数テーブルや変換表“意味のある位置”にだけ値がある構造を作りやすい。
大きい配列の最後だけ初期化[999] = 1 のようにピンポイント指定ができる。

例:最後だけ 1、それ以外は 0

int a[1000] = { [999] = 1 };

サンプルプログラム(曜日ごとの営業時間テーブルを要素指定子で作る)

「穴のある初期化」が直感的に分かる題材です。
店の営業時間(分)を「開店日のみ設定」して、休業日は0のままにします。

プロジェクト名:chap5-10-2 ソースファイル名:chap5-10-2.c

// 要素指定子で「一部だけ」初期化した配列を使う例
#include <stdio.h>

int main(void)
{
    // 0=Sun, 1=Mon, ... 6=Sat
    // 開店日のみ営業時間(分)を設定、休業日は0のまま
    int open_minutes[7] = {
        [1] = 480,   // Mon: 8 hours
        [2] = 480,   // Tue
        [3] = 480,   // Wed
        [4] = 480,   // Thu
        [5] = 360,   // Fri: 6 hours
        // Sun(0), Sat(6) は指定なし → 0
    };

    puts("曜日ごとの営業時間(分)を表示します:");
    for (int d = 0; d < 7; d++) {
        if (open_minutes[d] == 0)
            printf("Day %d: closed\n", d);
        else
            printf("Day %d: %d minutes\n", d, open_minutes[d]);
    }

    return 0;
}

このコードの読みどころ

添字(d)曜日イメージopen_minutes[d]意味
0Sun0closed
1Mon4808 hours
2Tue4808 hours
3Wed4808 hours
4Thu4808 hours
5Fri3606 hours
6Sat0closed

指定しなかった要素が 0 になるルールが、こういう用途と相性抜群です。

ここで覚えておきたい要点まとめ

項目覚えること
VLA実行時にサイズが決まる配列。便利だが移植性・安全性に注意。大きなサイズは危険。
要素指定子初期化で特定の添字に値を入れられる。穴あきテーブルに便利。指定しない要素は0。