ProgrammingEmbedded C 開発者

C言語において、多次元配列を宣言し、使用する方法について説明してください。関数に渡す際や初期化時にどのような落とし穴があるか教えてください。

Hintsage AIアシスタントで面接を突破

答え

問題の歴史

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]);

主な特徴:

  • 関数に渡す際には、最初の次元を除くすべての次元のサイズを明示的に指定する必要があります。
  • C言語における多次元配列は、配列の配列です; 要素は行優先順(row-major order)でメモリに配置されます。
  • 部分的な初期化では、指定されていない要素はゼロで初期化されます。

トリックの質問。

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. ポインタの配列と二次元配列の違いは何ですか?

二次元配列は単一のメモリブロックです。ポインタの配列は、別々に割り当てられる可能性のある一次元配列へのポインタの集合です。これは、「引き裂かれた」配列にメモリを割り当てる際に重要です。

一般的なエラーとアンチパターン

  • 多次元配列を使用した関数の宣言時のエラー(「内部」次元のサイズが指定されていない)。
  • 行優先レイアウトで作業している際に行と列が混同される。
  • ポインタの配列が真の多次元配列で置き換えられ、その逆も同様。

実生活の例

ネガティブケース

第二のサイズを指定せずに二次元配列を宣言し、関数に渡そうとし、コンパイルエラーを引き起こすこと。表面的な修正によるポインタへの置き換えは、その後の計算で未定義または不正な動作を引き起こします。

メリット:

  • 関数の宣言が簡単(初めて見ると)。

デメリット:

  • コンパイルエラー、インデックスの不正な算術、データの損傷。

ポジティブケース

開発者がすべての次元のサイズを明示的に指定し、ドキュメントにメモリ内の要素の保存順序を説明することで、メンテナンス中のエラーを減らします。

メリット:

  • 安全で正確かつ移植性のあるコード。

デメリット:

  • 配列のサイズや「幅」はコンパイル時に知られている必要があるか、C99規格と可変長配列が必要です。