Почему я должен предоставлять измерение при передаче двумерного массива функции C?

Я не уверен, что

Почему я не могу объявить подпись функции, такую ​​как:

void foo(int doubly_indexed_array[][]) {
  ...
}

который дает

$ gcc mem.c
mem.c:4: error: array type has incomplete element type

Почему вы должны объявлять один из параметров следующим образом?

void foo(int doubly_indexed_array[][10]) {
  ...
}

Ответ 1

Вам нужно объявить второй, а не один. Это связано с макетом памяти, 2-мерный массив хранится смежно в памяти, что означает, что все массивы второго измерения являются смежными.

Итак, для int[2][2] макет памяти выглядит (при условии, что инициализация равна 0):

[[0, 0][0, 0]]

Компилятор должен знать, как увеличить приращение указателя при индексации в первом измерении, например. Поэтому, если массив int имеет имя a,

a[i][j] действительно (address of a) + i*sizeof(int)*second_dimension + j*sizeof(int)

Все это нужно знать во время компиляции, чтобы компилятор мог генерировать код.

Ответ 2

Массив в C точно так же, как указатели, он не включает размер. Поэтому, если вы не указали последнее измерение, компилятор не будет знать, как вычислить адрес элемента

TYPE array[A][B];
&array[a][b] = (char*)array + a*sizeof(array[a]) + b
             = (char*)array + a*(B*sizeof(array[a][b])) + b
             = (char*)array + a*B*sizeof(TYPE) + b

Как вы видите, если B не объявлен, тогда у него есть 3 неизвестные переменные, которые будут решаться при обращении к array[a][b], что индекс измерения 2 и B. Поэтому компилятор нуждается в последнем размере размера для создания кода. Точно так же потребуются последние n-1 измерения n-мерного массива

Ответ 3

В сущности, все массивы в C одномерны. Чтобы иметь доступ к элементу по его индексу, C теперь должен быть типом элементов.

Рассмотрим одномерный массив из int. Поскольку C знает размер int (скажем, 4 байта), он знает, что для доступа к элементу 50 он просто добавляет 50 * 4 = 200 байт к базовому адресу массива. Поэтому он должен знать только базовый адрес и тип элементов, а не общее количество элементов (поскольку C не проверяет доступ за пределами диапазона, который в противном случае потребовал бы общий размер).

Теперь двумерный массив действительно представляет собой одномерный массив, элементы которого сами являются массивами. Чтобы получить доступ к элементу во внешнем массиве, вам нужно знать его "тип", который представляет собой массив определенного типа и размера.

Рассмотрим двумерный массив, объявленный как int a[100][10]. Так как C знает, что тип "внешнего" массива представляет собой массив из 10 целых чисел, он может вычислить позицию элемента (который сам является массивом) со смещением 50, добавив 50 * 4 * 10 к базовому адресу. Обратите внимание, что размер "внутреннего" массива необходим, чтобы найти положение элемента. С этого момента он делает то же, что и предыдущий пример, чтобы найти позицию внутри "внутреннего" массива запрошенного элемента int.

В целом вы должны объявить размеры всех измерений, кроме самого внешнего, чтобы C мог правильно обращаться к массиву.

Ответ 4

Объявление void foo(int array[][]) нарушает C 2011 (N1570) 6.7.6.2 1, в котором рассматриваются объявления массива и, в частности, говорится: "Тип элемента не должен быть неполным или типом функции". Поскольку array является массивом массива int, его тип элемента представляет собой массив из int, и он неполный, так как число int в этом массиве не указано.

В отличие от других ответов, это число не, необходимое компилятору в этот момент. Вы можете сделать эквивалентное объявление void foo(int (*array)[]). Здесь есть два момента:

  • Это не нарушает правило в 6.7.6.2, потому что оно объявляет array как указатель, а не массив. Указателям разрешено указывать на неполные типы.
  • Если первое объявление было разрешено, оно фактически будет таким же, как и это объявление, потому что в 6.7.6.3 сказано: "Объявление параметра как" массив типа "должно быть скорректировано на" квалифицированный указатель на тип ",...

Однако единственный способ доступа к элементам с этим объявлением - в форме (*array)[i]. Это законно, потому что оператор * может разыменовать указатель на неполный тип, поэтому *array является первой строкой массива, а затем (*array)[i] является элементом я th этой строки.

Вы не можете правильно обращаться к другим строкам массива, потому что для этого требуется выражение типа array[j][i], для которого требуется выполнить арифметику указателя на array, а для арифметики указателя требуется, чтобы указатель указывал на объект полного типа (потому что, для компилятора, чтобы выяснить, где находятся объекты за пределами того, на что они указывают, он должен знать, насколько велики объекты, поэтому он должен иметь полную информацию об их размере).