問題の歴史
C言語における多次元配列は、当初はテーブルや行列を簡単に扱うために設計されました。典型的な二次元配列は、テーブルデータの要素を簡単な構文で操作できる配列の配列です。時が経つにつれて、特に可変長配列を扱う際にアプローチが進化しました。
問題
多次元配列を誤って宣言および初期化すると、コンパイルエラーや論理的障害が発生します。多次元配列を関数に渡すときに、多くの開発者は仕様の要件に混乱します—最初の次元を除くすべてのサイズを明示的に指定する必要があります。
解決策
二次元配列の宣言:
int matrix[3][4];
完全な初期化:
int matrix[2][3] = { {1, 2, 3}, {4, 5, 6} };
関数に渡す際には、最初以外のすべてのサイズを明示的に指定する必要があります:
void printMatrix(int m[][3], int rows) { for (int i = 0; i < rows; ++i) { for (int j = 0; j < 3; ++j) printf("%d ", m[i][j]); printf(" "); } }
C99規格の導入により、可変長配列を受け取る関数を宣言することができます:
void foo(int rows, int cols, int a[rows][cols]);
主な特徴:
1. 二次元配列を受け取る関数を、二番目のサイズを指定せずに宣言できますか?
いいえ、C言語では、最初の次元を除くすべてのサイズがコンパイル時に知られている必要があります。これは要素にアクセスする際のポインタ演算に関連しています。
エラーの例:
// エラー: void process(int arr[][], int rows); // できません
2. 多次元配列のすべての要素を初期化しないとどうなりますか?
残りの要素は、自動的にゼロで埋められます。配列が静的またはグローバルの場合、部分的な初期化をしたローカル配列でも、暗黙的に初期化された要素もゼロになります。
int a[2][3] = {{1}, {4}}; // a[0][1] と a[0][2], a[1][1] と a[1][2] は0になります
3. ポインタの配列と二次元配列の違いは何ですか?
二次元配列は単一のメモリブロックです。ポインタの配列は、別々に割り当てられる可能性のある一次元配列へのポインタの集合です。これは、「引き裂かれた」配列にメモリを割り当てる際に重要です。
第二のサイズを指定せずに二次元配列を宣言し、関数に渡そうとし、コンパイルエラーを引き起こすこと。表面的な修正によるポインタへの置き換えは、その後の計算で未定義または不正な動作を引き起こします。
メリット:
デメリット:
開発者がすべての次元のサイズを明示的に指定し、ドキュメントにメモリ内の要素の保存順序を説明することで、メンテナンス中のエラーを減らします。
メリット:
デメリット: