Почему нам нужно указывать размер столбца при передаче 2D-массива в качестве параметра?
Почему мой параметр не может быть
void example(int Array[][]){ /*statements*/}
Почему мне нужно указать размер столбца массива? Скажем, например, 3
void example(int Array[][3]){/*statements*/}
Мой профессор сказал, что он является обязательным, но я был кодированием до начала школы, и я вспомнил, что не было синтаксической или семантической ошибки, когда я сделал этот параметр? Или я что-то пропустил?
Ответы
Ответ 1
Когда дело доходит до описания параметров, массивы всегда распадаются на указатели на их первый элемент.
Когда вы передаете массив, объявленный как int Array[3]
, функции void foo(int array[])
, он распадается на указатель на начало массива i.e. int *Array;
. Btw, вы можете описать параметр как int Array[3]
или int array[6]
или даже int *array
- все это будет эквивалентно, и вы можете без проблем передать любой целочисленный массив.
В случае массивов массивов (2D-массивов) он также распадается на указатель на его первый элемент, который оказывается одномерным массивом, то есть мы получаем 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
является указателем на одномерный массив и применяется та же формула. Количество байтов в последней квадратной скобке требуется для поиска size_of_item_being_pointed_by_a
. Если бы у нас было просто Array[][]
, было бы невозможно найти его и, следовательно, невозможно разыменовать элемент массива, который нам нужен.
Без размера, арифметика указателей не будет работать для массивов массивов. Каким будет адрес Array + 2
: продвиньте адрес в Array
2 байта вперед (неправильно) или переместите указатель 3* sizeof(int) * 2
байты вперед?
Ответ 2
В C/С++ даже 2-D массивы сохраняются последовательно, одна строка за другой в памяти. Итак, когда у вас (в одной функции):
int a[5][3];
int *head;
head = &a[0][0];
a[2][1] = 2; // <--
Элемент, к которому вы действительно обращаетесь с a[2][1]
, *(head + 2*3 + 1)
, вызывает последовательно, этот элемент после трех элементов строки 0
и 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)
, потому что функция знает размер второго измерения.
Существует еще один вариант, как было предложено ранее, вы можете объявить такую функцию, как:
void some_function(int *array, int cols) { ... }
потому что таким образом вы вызываете функцию с той же "информацией", что и раньше, - количеством столбцов. Вы обращаетесь к элементам массива немного иначе: вам нужно написать *(array + i*cols + j)
, где вы обычно пишете array[i][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;
}
Ответ 3
В этом отношении есть аналогичная статья. Вы можете ссылаться на ссылку ниже.
Создание массива в C и передача указателя на указанный массив
Надеюсь, что это поможет.
С другой стороны, компилятор нуждается во втором измерении, чтобы он мог перемещать "Массив" с одного указателя на следующий, поскольку вся память расположена линейным образом
Ответ 4
Когда вы создаете 2D-массив, anytype a[3][4]
, в памяти то, что вы на самом деле создаете, является 3
смежными блоками объектов 4
anytype.
a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]
Теперь следующий вопрос: почему это так? Поскольку, соблюдая спецификацию и структуру языка, anytype a[3][4]
фактически расширяется в anytype (*a)[4]
, потому что массивы распадаются на указатели. И на самом деле это также распространяется на anytype (*(*a))
, однако теперь вы полностью потеряли размер 2D-массива. Итак, вы должны немного помочь компилятору.
Если вы запросите программу для a[2]
, программа может выполнить те же действия, что и для 1D-массивов. Он просто может вернуть the 3rd element of sizeof(object pointed to)
, объект, на который указывает здесь, имеет объекты anytype размером 4.
Ответ 5
Все это связано с представлением внутреннего массива. Таким образом, массив, подобный int arr_2 [2][2] = {{1,2},{3,4}};
, выглядит в памяти как 1,2,3,4
, каждый из которых является целым типом. arr_2
примерно эквивалентен &arr[0][0]
(указатель на первый элемент) при передаче функции.
Обычные массивы хранятся и получаются аналогично int arr[2] = {2,5};
в памяти 2,5
. Компилятор добирается до них через свою базу плюс время смещения типа base_ptr + index * type
. Чтобы получить значение, хранящееся в индексе 1 типа int, пишем &arr[0] + 1 * sizeof(int) = 5
Итак, двумерные массивы доступны так base_ptr + type * 2nd_dem_len + index
. Чтобы получить значение, сохраненное в [1] [1], пишем &arr_2[0][0] + sizeof(int) * 1 + 1 = 4
Поэтому, если компилятор не знает 2nd_dem_len
или длину второго измерения, он не может получить доступ к этому массиву. Он не знает этого, потому что массив попадает в это представление при передаче функции, потому что компилятор преобразует его в указатель, который не имеет информации о размере.
Ответ 6
Собственно, будь то массив 2d или 1d, он сохраняется в памяти в одной строке. Поэтому, чтобы сказать компилятор, где он должен разорвать строку, указывающую, что следующие числа будут в следующих строках, мы предполагаем для обеспечения размера столбца. И разбиение строк соответствующим образом даст размер строк.
Посмотрим на пример:
int a[][3]={ 1,2,3,4,5,6,7,8,9,0 };
Этот массив a сохраняется в памяти как:
1 2 3 4 5 6 7 8 9 0
Но поскольку мы указали размер столбца как 3, память разбивается после каждых трех чисел.
#include<stdio.h>
int main() {
int a[][3]={1,2,3,4,5,6},i,j;
for(i=0;i<2;i++)
{
for(j=0;j<3;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
}
ВЫВОД:
1 2 3
4 5 6
В другом случае
int a[3][]={1,2,3,4,5,6,7,8,9,0};
Компилятор знает только, что есть 3 строки, но он не знает количества элементов в каждой строке, поэтому он не может выделить память и будет показывать ошибку.
#include<stdio.h>
int main() {
int a[3][]={1,2,3,4,5,6},i,j;
for(i=0;i<3;i++)
{
for(j=0;j<2;j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
}
ВЫВОД:
c: In function 'main':
c:4:8: error: array type has incomplete element type 'int[]'
int a[3][]={1,2,3,4,5,6},i,j;
^