Почему c/С++ позволяет пропускать самый левый индекс многомерного массива в вызове функции?

Мне просто интересно, почему при передаче массива функции нужно опустить самый левый индекс многомерного массива? Почему не более одного индекса? И как компилятор узнает размер с одним пропущенным индексом?

Ответы

Ответ 1

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

В C массивы обозначают плотно упакованные элементы в памяти.

A -> _ _ _ _ _ _ ...
i:   0 1 2 3 4 5 ...

В приведенном выше примере каждый из элементов массива имеет ширину 1 _. Чтобы найти i-й элемент, мы должны перейти к i-му адресу. (Обратите внимание, что самое левое измерение (размер) здесь не имеет значения)

Теперь рассмотрим многомерный массив:

B -> [_ _ _][_ _ _][_ _ _][_ _ _]...
i:    0 0 0  1 1 1  2 2 2  3 3 3
j:    0 1 2  0 1 2  0 1 2  0 1 2
     ^first row    ^third row

Чтобы найти смещение A[i][j], нам нужно перепрыгнуть через я строк (3 * i), а затем над j элементами → (3 * я + j). Обратите внимание, что размер первого измерения здесь также не требуется.

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


Поскольку нет необходимости давать размерность самого левого индекса, то почему бы не дать его в любом случае ради полноты? В конце концов, это то, что делается на языке программирования Pascal (современный C).

Ну, большинство функций, которые работают на массивах, работают одинаково для всех возможных длин массивов, поэтому указание размера может только повредить вашей способности повторно использовать их.

например, почему

int sum(int arr[10]){
    int s = 0, i;
    for(i=0; i<10; i++){
        s += arr[i];
    }
    return s;
}

Если вы можете сделать это вместо этого:

int sum(int arr[], int n){
    int s = 0, i;
    for(i=0; i<n; i++){
        s += arr[i];
    }
    return s;
}

Что касается исключения более одного измерения, это невозможно при использовании обычных многомерных массивов (потому что вам нужно знать измерение, чтобы знать, когда заканчивается первая строка, а вторая начинается). Однако, если вы готовы потратить некоторую (небольшую) дополнительную память на пространство с нуля, вполне возможно использовать указатели вместо указателей: http://www.eskimo.com/~scs/cclass/int/sx9b.html

Ответ 2

В объявлении

Собственно, вы не можете полностью исключить крайнее правое или левое измерение.

Тем не менее, только левый может быть выведен для вас, если у вас есть инициализатор.

В списке аргументов функции

Когда вы передаете массив в функцию по значению, вы фактически передаете указатель на первый элемент этого массива. Да, это выглядит из синтаксиса, как будто вы передаете массив, но нет, вы не знаете.

Рассмотрим:

void f(int ar[3])

void f(int ar[])

Оба путают синтаксис для эквивалента:

void f(int* ar)

Отсутствие следа массива, не говоря уже о одном из специально трех элементов.

Сейчас:

void f(int ar[][3])

Это запутанный синтаксис для эквивалента:

void f(int (*ar)[3])

где int (*)[3] - это тип указателя на первый элемент вашего массива (указатель на int[3]).

В заключение не обращайте слишком много внимания на синтаксис типа массива, который выглядит как []; это действительно не отражает того, что действительно происходит.

Ответ 3

За исключением случаев, когда он является операндом операторов sizeof или унарных &, или является строковым литералом, используемым для инициализации массива в объявлении, выражение типа "N-element array of T" будет иметь свой тип, неявно преобразованный в "указатель на T и будет оценивать адрес первого элемента в массиве.

Какое отношение это имеет к вашему вопросу?

Предположим следующие строки кода:

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
foo(arr);

Мы передаем выражение массива arr в качестве аргумента foo. Поскольку arr не является операндом либо sizeof, либо &, его тип неявно преобразуется из "10-элементного массива int" в "указатель на int". Таким образом, мы передаем значение указателя foo, а не массив.

Оказывается, что в объявлении параметра функции T a[] и T a[N] являются синонимами для T *a; все три объявляют a как указатель на T, а не массив T.

Мы можем написать определение прототипа для foo как

void foo(int *a)     // <- foo receives a pointer to int, not an array

или

void foo(int a[])    // <-- a[] is a synonym for *a

Оба означают одно и то же; оба объявляют a как указатель на int.

Теперь посмотрим на многомерные массивы. Предположим, что следующий код:

int arr[10][20];
foo(arr);

Выражение arr имеет тип "10-элементный массив из 20-элементного массива int". По правилу, описанному выше, он будет неявно преобразован в "указатель на 20-элементный массив int". Таким образом, определение прототипа для foo можно записать в виде

void foo(int (*a)[20])  // <-- foo receives a pointer to an array, not an array of arrays

или

void foo(int a[][20])  // <-- a[][20] is a synonym for (*a)[20]

Опять же, объявить a как указатель, а не массив.

Вот почему вы можете отбросить самый левый (и только самый левый) индекс массива в объявлении параметра функции.