. Programming Place Plus C言語編 第25章
Programming Place Plus C言語編 第25章
Programming Place Plus C言語編 第25章

配列 | Programming Place Plus C言語編 第25章

このプログラムでは、変数i が負数になりえないので大丈夫ですが、int型と size_t型の比較は、第21章で解説したような問題を起こす可能性があります。たとえば、size_t型が unsigned int型である処理系では、int型と unsigned型の比較になるので、通常の算術変換(第21章)により、int型の側が unsigned int型に変換されてから比較されます。そのため、int型の側が負数だった場合、巨大な正の整数になってしまい、想定外の比較結果を生み出します。

【上級】あるいは、配列array の要素数が、int型で表現できないほど極端に巨大な場合、変数i の値が、変数size の値にまで到達できないため、for文を終わらせることができないことになります。

問題にならないことも多いため、あまり考えずに書かれているプログラムが多いですが、C言語でのプログラミングの一般論として、符号の有無の混在には注意を払わなければなりません。たとえば、変数i も size_t型にする意味はあります。

#include int main(void) int array[] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9>; const size_t size = sizeof(array) / sizeof(array[0]); for (size_t i = 0; i size; ++i) printf("%d ", array[i]); > printf("\n"); > 0 1 2 3 4 5 6 7 8 9

可変長配列 🔗

配列の要素数の指定に定数式以外、つまり変数を使うことができる場面があります。このような配列は、 可変長配列 📘 (variable length array、VLA) と呼ばれます。ただし、 Visual Studio 2017/2019/2022 は、この機能に対応していません。

【C89/95 経験者】この機能は C99 で追加されたものです。

【C11】この機能はオプションとなり、提供しない処理系も許されるように変更されました。可変長配列や、それを指すポインタ型 📘 を提供しない場合、 1 に置換される __STDC_NO_VLA__ という事前定義マクロが定義されます。 [3]

【C23】__STDC_NO_VLA__ が 1 に置換される場合、自動記憶域期間を持つ可変長配列をサポートしないという意味に変更されました [6] 。

【C++ プログラマー】この機能は C++ では使えません。

可変長配列の要素数は、その宣言を行っている箇所に処理が到達したときに、式を評価して決定します。1度確定した要素数は変化しませんが、同じ宣言を何度も通過する場合、そのたびに異なる要素数になる可能性はあります。なお、要素数は 0 より大きい正の整数にならなければなりません。

可変長配列が使える1つ目の場面として、 宣言しようとしている配列が、ブロックスコープ 📘 (第22章)かつ自動記憶域期間(第21章)を持つ場合です。 たとえば次のように使います。

#include int main(void) puts("Please enter the number of data."); char buf[40]; fgets(buf, sizeof(buf), stdin); int data_num; sscanf(buf, "%d", &data_num); // データ件数が 0件以下なら終了 if (data_num 0) return 0; > // 件数に合わせた大きさの可変長配列を宣言 int data_array[data_num]; // データを受け取る for (int i = 0; i data_num; ++i) puts("Please enter the integer."); fgets(buf, sizeof(buf), stdin); sscanf(buf, "%d", &data_array[i]); > // 結果を出力 for (int i = 0; i data_num; ++i) printf("%d: %d\n", i, data_array[i]); > > Please enter the number of data. 5

可変長配列が使用できる2つ目の場面は、 関数プロトタイプスコープを持つ場合です。 つまりは、関数の他の仮引数を要素数の指定に使えます。

#include void print_array(int n, int array[n]); int main(void) puts("Please enter the number of data."); char buf[40]; fgets(buf, sizeof(buf), stdin); int data_num; sscanf(buf, "%d", &data_num); // データ件数が 0件以下なら終了 if (data_num 0) return 0; > int array[data_num]; for (int i = 0; i data_num; ++i) array[i] = 999; > print_array(data_num, array); > void print_array(int n, int array[n]) for (int i = 0; i n; ++i) printf("%d\n", array[i]); > > Please enter the number of data. 5

前に書いたとおり、配列は直接的に引数としては使えません。これは可変長配列であっても同様です。ですから実は、 仮引数に可変長配列を指定しても、実質的にそこで指定した要素数は意味を成していません 。そこで、 添字の部分を * としても、可変長配列であることを表現できます。これができるのは、関数プロトタイプスコープの場合だけなので、関数宣言では可能ですが、関数定義では不可能です。

// 関数宣言 void print_array(int n, int array[*]); // 関数定義 void print_array(int n, int array[n]) >

要素指示子 🔗

要素指示子 (designated initializer) という機能を使うと、配列の特定の要素を選んで初期値を与えられます。この機能は配列以外でも使える場面がありますが、ここでは配列についての解説にとどめます。

【C89/95 経験者】この機能は C99 で追加されたものです。

【C++ プログラマー】C++20 で、 を使った集成体初期化の際に、要素指示子を使えるようになりましたが、配列に対しては使えません。 [4]

#include #define ARRAY_SIZE 10 int main(void) int array[ARRAY_SIZE] = [0] = 10, [ARRAY_SIZE - 1] = 99 >; for (int i = 0; i ARRAY_SIZE; ++i) printf("%d ", array[i]); > printf("\n"); > 10 0 0 0 0 0 0 0 0 99

配列array の初期化のところを見てください。 [0] = 10 のように、どの要素に対する初期化子であるのかを明示的に書くことによって、 array[0] に対して 10 を与えられます。 [ARRAY_SIZE - 1] = 99 のように計算式を使うことも可能です。

初期値が明示されていない array[1] ~ array[ARRAY_SIZE - 2] の範囲の要素については、静的記憶域期間の場合に与えられるデフォルトの初期値の規則と同様に、 0 に相当する値で初期化されます 。

#include #define ARRAY_SIZE 10 int main(void) int array[ARRAY_SIZE] = [0] = 10, 11, 12, [5] = 50, 51 >; for (int i = 0; i ARRAY_SIZE; ++i) printf("%d ", array[i]); > printf("\n"); > 10 11 12 0 0 50 51 0 0 0

要素指示子による初期化の次に書いた要素指示子を使わない初期化は、続きの要素に対する初期化とみなされます。 そのため、 11 は array[1] の初期化子であり、 51 は array[6] に対する初期化子です。

#include int main(void) int array[] = [0] = 10, 11, 12, [5] = 50, 51 >; printf("%zu\n", sizeof(array) / sizeof(array[0])); >

この場合、51 を与えた array[6] の添字が一番大きいので、要素数は 7 になります。

#include int main(void) int array[] = [5] = 50, [4] = 40, 51 // [5] = 51 を意味している >; size_t size = sizeof(array) / sizeof(array[0]); for (size_t i = 0; i size; ++i) printf("%d ", array[i]); > printf("\n"); > 0 0 0 0 40 51

51 という初期化子は、要素指示子による array[4] の初期化の直後にありますから、 array[5] に対するものとみなされます。そのため、 array[5] にはすでに 50 を与えているはずですが、上書きされて 51 になります。

複合リテラル 🔗

複合リテラル (compound literal) を使うと、配列型のリテラルを記述できます。この機能を解説するには、ポインタという別の機能の前提知識が必要ですので、第32章であらためて取り上げます。

【C89/95 経験者】この機能は C99 で追加されたものです。

練習問題 🔗

問題① 要素数10 の int型配列に、2 から始まる 2 のべき乗を順番に格納し、それを逆の順番で表示するプログラムを作成してください。

char str[] = "abcdef"; char str[] = "abc\0def"; #define ARRAY_SIZE 5 int values[ARRAY_SIZE] = 13, 27, 75, 27, 48>; #define ARRAY_SIZE 5 int values1[ARRAY_SIZE] = 17, 8, 29, -5, 13>; int values2[ARRAY_SIZE] = 64, -5, 17, -22, -38>;

参考リンク 🔗

  • 1 . ISO/IEC 9899:TC3 WG14/N1256 Committee Draft — Septermber 7, 2007 | 6.7.8 Initialization - 21 (PDF直リンク)
  • 2 . ISO/IEC 9899:TC3 WG14/N1256 Committee Draft — Septermber 7, 2007 | 6.7.8 Initialization - 2 (PDF直リンク)
  • 3 .
    • ISO/IEC 9899:201x N1570 Committee Draft — April 12, 2011 | 6.7.6.2 Array declarators - 4 (PDF直リンク)
    • ISO/IEC 9899:201x N1570 Committee Draft — April 12, 2011 | 6.10.8.3 Conditional feature macros - 1 (PDF直リンク)
    • ISO/IEC 9899:202y (en) — n3301 working draft | 6.7.11 Initialization - 4 (PDF直リンク)
    • ISO/IEC 9899:202y (en) — n3301 working draft | 6.7.11 Initialization - 11 (PDF直リンク)

    更新履歴 🔗

    • 2024/12/15
      • 「可変長配列」に、C23 で可変長配列をサポートするルールが変更されたことについてのコラムを追加
      • 「配列の初期化」に、C23 の空の初期化子による初期化についてのコラムを追加
      • 全体的に見直し修正
      • タイトルを「配列と文字列」から「配列」に変更
      • コーディング規約を統一(変数や関数の名前をスネークケースにする)
      • 初出の重要用語に英語表記を併記
      • 2023/2/5
        • コーディング規約を統一(for、if などの () の前後の空白の空け方)
        • コーディング規約を統一(実引数がある関数呼び出しの ( の直後、 ) の直前に空白を入れない)
        • 「文字列」の項を、第8章へ移動
        • main関数から return 0; を削除(C言語編全体でのコードの統一)
        • 「要素指示子」に、要素指示子を使わない初期化を混ぜた場合の動作について追記
        • 解説のベースを C99 に上げる対応
          • 「複合リテラル」の項を追加
          • 解説のベースを C99 に上げる対応
            • ローカル変数の宣言を、ブロックの先頭以外の位置でもおこなう
            • 解説のベースを C99 に上げる対応
              • コメントを // 形式で統一
              • 解説のベースを C99 に上げる対応
                • ループ制御変数を for文の初期設定式で宣言するように修正
                • 解説のベースを C99 に上げる対応
                  • 「C99 (要素指示子)」の項を「要素指示子」に変更
                  • 「C99 (可変長配列)」の項を「可変長配列」に変更
                  • 第30章から練習問題⑤⑥を移動してきて、練習問題④⑤とした。
                  • 「配列の初期化」の項を修正。 明示的に初期化しなかったとき、静的記憶域期間を持つ場合は初期化されることを追記。
                  • 用語を統一(文字列定数 -> 文字列リテラル)
                  • Visual Studio 2013 の対応終了。
                  • 「VisualC++」という表現を「Visual Studio」に統一。
                  • 全面的に文章を見直し、修正を行った。 第34章から「C99 (可変長配列)」の項を移動してきた。内容も修正。 「範囲外アクセス」「配列のメモリイメージ」「配列を引数や戻り値にするには」の項を削除し、 これらの項の内容を「配列」の項に入れ込んだ。
                  • コンパイラの対応状況について、対応している場合は明記しない方針にした。
                  • clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。
                  • VisualC++ 2017 に対応。
                  • clang の対応バージョンを 3.7 に更新。
                  • SIZE_OF_ARRAYマクロの定義を修正。
                  • clang の対応バージョンを 3.4 に更新。
                  • VisualC++ 2012 の対応終了。
                  • 配列の要素数を関数形式マクロで求める方法について、コラムとして追記。
                  • VisualC++ 2010 の対応終了。
                  • VisualC++ 2015 に対応。
                  • clang 3.2 に対応。
                  • VisualC++ 2013 に対応。
                  • VisualC++ 2008 の対応終了。
                  • clang 3.0 に対応。
                  • 「C99 (指示付きの初期化子)」の項を追加。
                  • 新規作成