web-dev-qa-db-ja.com

2D配列をパラメーターとして渡すときに列サイズを指定する必要があるのはなぜですか?

なぜ私のパラメータはできません

void example(int Array[][]){ /*statements*/}

なぜ配列の列サイズを指定する必要があるのですか?たとえば、3と言います

void example(int Array[][3]){/*statements*/}

教授は必須だと言いましたが、私は学校が始まる前にコーディングをしていましたが、これを私のパラメーターにしたときに構文エラーや意味エラーがないことを思い出しましたか?または私は何かを逃したのですか?

34

パラメータを記述する場合、配列は常に最初の要素へのポインタに減衰します。

_int Array[3]_として宣言された配列を関数void foo(int array[])に渡すと、配列の先頭へのポインタ、つまり_int *Array;_に減衰します。ところで、パラメーターを_int array[3]_または_int array[6]_または_int *array_として記述することもできます。これらはすべて同等であり、任意の整数配列を問題なく渡すことができます。

配列の配列(2D配列)の場合、最初の要素へのポインターにも減衰します。これはたまたま1次元配列です。つまり、int (*Array)[3]を取得します。

ここでサイズを指定することが重要です。必須ではなかった場合、たとえば、コンパイラが式_Array[2][1]_の処理方法を知る方法はありません。

コンパイラーがメモリの連続したブロック(_int Array[2][3]_は整数の連続したブロックです)で必要なアイテムのオフセットを計算する必要があることを逆参照するには、ポインターが簡単です。 aがポインターの場合、_a[N]_は_start_address_in_a + N * size_of_item_being_pointed_by_a_として展開されます。関数内の式_Array[2][1]_の場合(この要素にアクセスしたい)、Arrayは1次元配列へのポインターであり、同じ式が適用されます。 _size_of_item_being_pointed_by_a_を見つけるには、最後の角括弧内のバイト数が必要です。 _Array[][]_しかなかった場合、それを見つけることは不可能であり、したがって必要な配列要素を逆参照することは不可能です。

サイズがなければ、ポインター演算は配列の配列では機能しません。 _Array + 2_が生成するアドレス:Arrayでアドレスを2バイト先に(間違って)進めるか、ポインタを3* sizeof(int) * 2バイト先に進めますか?

35

C/C++では、2次元配列であっても、メモリ内に1行ずつ順番に格納されます。だから、あなたが持っているとき(単一の関数で):

_int a[5][3];
int *head;

head = &a[0][0];
a[2][1] = 2; // <--
_

_a[2][1]_で実際にアクセスしている要素は*(head + 2*3 + 1)であり、順次発生します。この要素は、_0_行の3つの要素と_1_行の3つの要素の後にあります、さらに1つのインデックスを追加します。

次のような関数を宣言した場合:

_void some_function(int array[][]) {...}
_

構文的には、エラーではありません。ただし、ここで_array[2][3]_にアクセスしようとすると、どの要素にアクセスする必要があるかがわかりません。一方、次の場合:

_void some_function(int array[][5]) {...}
_

_array[2][3]_を使用すると、実際にメモリアドレスの要素にアクセスしていると判断できます*(&array[0][0] + 2*5 + 3)原因関数は2番目の次元のサイズを知っています。

前に提案したように、他に1つのオプションがあり、次のような関数を宣言できます。

_void some_function(int *array, int cols) { ... }
_

この方法では、以前と同じ「情報」、つまり列の数で関数を呼び出しているためです。次に、配列要素へのアクセス方法が少し異なります。通常は_array[i][j]_を書き込む場所に*(array + i*cols + j)を記述する必要があります。これにより、arrayは整数へのポインタになります(ポインタではありません) )。

このような関数を宣言するときは、使用されるだけでなく、配列に対して実際に宣言されている列の数を使用して関数を呼び出すように注意する必要があります。したがって、たとえば:

_int main(){
   int a[5][5];
   int i, j;

   for (i = 0; i < 3; ++i){
       for (int j=0; j < 3; ++j){
           scanf("%d", &a[i][j]);
       }
   }

   some_function(&a[i][j], 5); // <- correct
   some_function(&a[i][j], 3); // <- wrong

   return 0;
}
_
10
penelope

C 2018 6.7.6.2は配列宣言子のセマンティクスを指定し、パラグラフ1はそれらに次のような制約を与えます:

要素タイプは、不完全または関数タイプであってはなりません。

void example(int Array[][])などの関数宣言では、_Array[]_は配列宣言子です。そのため、その要素タイプが不完全であってはならないという制約を満たさなければなりません。その宣言の要素タイプは_int []_で、サイズが指定されていないため不完全です。

C標準がポインターに調整されるパラメーターの制約を削除できなかった根本的な理由はありません。結果の型int (*Array)[]は正当な宣言であり、コンパイラーによって受け入れられ、_(*Array)[j]_の形式で使用できます。

ただし、宣言_int Array[][]_は、Arrayが少なくとも2次元配列に関連付けられていることを示唆しているため、_Array[i][j]_の形式で使用されます。宣言_int Array[][]_が受け入れられてint (*Array)[]に調整されたとしても、それを_Array[i][j]_として使用することは不可能です。要素のアドレスを計算する必要があるため、この要件は避けられません。したがって、引数が1つの1次元配列へのポインタではなく、2次元配列になるという意図された式と一致しているため、配列宣言子の制約を維持することには意味があります。

1

これに関する同様の投稿があります。以下のリンクを参照してください。 Cで配列を作成し、その配列へのポインターを関数に渡す 役立つことを願っています。

一方、メモリ全体が線形に配置されているため、コンパイラは、「配列」を1つのポインタから次のポインタに移動できるように、2次元にする必要があります。

0
Manik Sidana