Путаница в отношении указателей и многомерных массивов

Если возможно следующее:

MyFunction(int *array, int size) { for(int i=0 ; i<size ; i++) { printf(“%d”, array[i]); } } main() { int array[6] = {0, 1, 2, 3, 4, 5}; MyFunction(array, 6); } 

Почему следующее не так?

 MyFunction(int **array, int row, int col) { for(int i=0 ; i<row ; i++) { for(int j=0 ; j<col ; j++) { printf(“%d”, array[i][j]); } } } main() { int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; MyFunction(array, 3, 3); } 

Во-первых, некоторый стандартный язык:

6.3.2.1 Lvalues, массивы и указатели функций

3 За исключением случаев, когда это операнд оператора sizeof или унарный оператор & или строковый литерал, используемый для инициализации массива, выражение, которое имеет тип «массив типа», преобразуется в выражение с типом «указатель на тип», который указывает на начальный элемент объекта массива и не является значением lvalue. Если объект массива имеет class хранения регистров, поведение не определено.

Учитывая декларацию

 int myarray[3][3]; 

тип myarray – это «3-элементный массив из 3-элементного массива int ». Следуя приведенному выше правилу, когда вы пишете

 MyFunction(myarray, 3, 3); 

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

Таким образом, ваш прототип функции должен быть

 int MyFunction(int (*array)[3], int row, int col) 

Обратите внимание, что int **array не совпадает с int (*array)[3] ; арифметика указателя будет отличаться, поэтому ваши индексы не будут указывать на нужные места. Помните, что индексирование массива определяется в терминах арифметики указателя: a[i] == *(a+i) , a[i][j] == *(*(a + i) + j) . a+i даст другое значение в зависимости от того, a ли a int ** или int (*)[N] .

В этом конкретном примере предполагается, что вы всегда передаете массив элементов Nx3 из int ; не очень гибкий, если вы хотите иметь дело с любым размером NxM. Один из способов обойти это – это явно передать адрес первого элемента в массиве, поэтому вы просто передаете простой указатель, а затем вычислите правильное смещение вручную:

 void MyFunction(int *arr, int row, int col) { int i, j; for (i = 0; i < row; i++) for (j = 0; j < col; j++) printf("%d", a[i*col+j]); } int main(void) { int myarray[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; ... MyFunction(&myarray[0][0], 3, 3); 

Поскольку мы передаем простой указатель на int , мы не можем использовать двойной индекс в MyFunc ; результат arr[i] является целым числом, а не указателем, поэтому мы должны вычислить полное смещение в массив в одной операции индекса. Обратите внимание, что этот трюк будет работать только для действительно многомерных массивов.

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

 void AnotherFunc(int **arr, int row, int col) { int i, j; for (i = 0; i < row; i++) for (j = 0; j < col; j++) printf("%d", arr[i][j]); } int main(void) { int d0[3] = {1, 2, 3}; int d1[3] = {4, 5, 6}; int d2[3] = {7, 8, 9}; int *a[3] = {d0, d1, d2}; AnotherFunc(a, 3, 3); ... } 

Следуя правилу выше, когда выражения d0 , d1 и d2 появляются в инициализаторе для a , их типы все преобразуются из «3-элементного массива из int » в «pointer to int ». Аналогично, когда выражение a появляется в вызове AnotherFunc , его тип преобразуется из «3-элементного массива указателя в int » в «указатель на указатель на int ».

Обратите внимание, что в AnotherFunc мы AnotherFunc обе измерения вместо вычисления смещения, как в MyFunc . Это потому, что a - это массив значений указателя . Выражение arr[i] получает значение i-го значения указателя из местоположения arr ; мы затем находим j'th целочисленное значение, смещенное от этого значения указателя.

Следующая таблица может помочь - она ​​показывает типы различных выражений массива и их распад на основе их объявлений ( T (*)[N] - тип указателя, а не тип массива, поэтому он не распадается):

 Тип выражения выражения, неявно преобразованный (распады) в
 ----------- ---------- ---- ------------------------- -------
      T a [N] a T [N] T *
                                & a T (*) [N]
                                *в
                              a [i] T

   T a [M] [N] a T [M] [N] T (*) [N]
                                & a T (*) [M] [N] 
                                * a T [N] T *
                              a [i] T [N] T *
                             & a [i] T (*) [N] 
                             * a [i] T
                           a [i] [j] T

 T a [L] [M] [N] a T [L] [M] [N] T (*) [M] [N]
                                & a T (*) [L] [M] [N]
                                * a T [M] [N] T (*) [N]
                              a [i] T [M] [N] T (*) [N]
                             & a [i] T (*) [M] [N]
                             * a [i] T [N] T *
                           a [i] [j] T [N] T *
                          & a [i] [j] T (*) [N]
                          * a [i] [j] T 
                        a [i] [j] [k] T

Шаблон для многомерных массивов должен быть ясным.

Изменить: Вот моя попытка ответить на более точный ответ в соответствии с запросом и на основе вашего нового примера кода:

Независимо от размеров массива, то, что вы передаете, является «указателем на массив» – это всего лишь один указатель, хотя тип указателя может меняться.

В вашем первом примере int array[6] представляет собой массив из 6 элементов int . Передающий array передает указатель на первый элемент, который является int , поэтому тип параметра – int * , который может быть эквивалентно записан как int [] .

В вашем втором примере int array[3][3] представляет собой массив из трех строк (элементов), каждый из которых содержит 3 int s. Передача array передает указатель на первый элемент, который представляет собой массив из 3 int s . Следовательно, тип int (*)[3] – указатель на массив из 3 элементов, который может быть эквивалентно записан как int [][3] .

Надеюсь, теперь вы видите разницу. Когда вы передаете int ** , это на самом деле указатель на массив int * s и NOT – указатель на 2D-массив.

Пример для фактического int ** будет выглядеть примерно так:

 int a[3] = { 1, 2, 3 }; int b[3] = { 4, 5, 6 }; int c[3] = { 7, 8, 9 }; int *array[3] = { a, b, c }; 

Здесь array представляет собой массив из 3 int * s, и передача этого аргумента приведет к int ** .


Оригинальный ответ:

Ваш первый пример – не 2D-массив, хотя он используется аналогичным образом. Там вы создаете ROWS число указателей char * , каждый из которых указывает на другой массив символов COLS . Здесь есть два уровня косвенности.

Второй и третий примеры представляют собой 2D-массивы, где память для всех ROWS * COLS смежна. Здесь существует только один уровень косвенности. Указатель на 2D-массив не char ** , а char (*)[COLS] , поэтому вы можете сделать:

 char (*p)[SIZE] = arr; // use p like arr, eg. p[1][2] 

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

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

Кроме того, у этого есть хорошая информация: http://c-faq.com/aryptr/index.html

Первый пример возможен, потому что массивы вырождаются в указатели при передаче в качестве параметров функции.

Второй пример не работает, потому что int[3][3] вырождается в int (*)[3] , а не двойной указатель int ** . Это так, потому что 2D-массивы смежны в памяти, и без этой информации компилятор не знал, как получить доступ к элементам, находящимся за первой строкой. Рассмотрим простую сетку чисел:

 1 2 6 0 7 9 

Если бы мы сохраняли эти числа в массиве int nums[6] , как бы мы индексировали в массив для доступа к элементу 7? По 1 * 3 + 1 , конечно, или, более широко, row * num-columns + column . Чтобы получить доступ к любому элементу за первой строкой, вам нужно знать, сколько столбцов имеет grid.

Когда вы сохраняете числа как nums[2][3] , компилятор использует ту же row * num-columns + column арифметику row * num-columns + column что и вручную с 1D-массивом, он просто скрыт от программиста. Поэтому вам необходимо передать количество столбцов при передаче 2D-массива, чтобы компилятор мог выполнить эту арифметику.

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

Возможно, мы можем ожидать более «точного» вопроса, если вы хотите получить более точный ответ. У вашей идеи две проблемы:

  1. 2D-массив int A[3][3] при использовании в выражении распадается на адрес его первого элемента, таким образом, на указатель типа int (*)[3] . Чтобы иметь возможность передать массив, вы должны использовать &A[0][0] чтобы получить указатель на первый «внутренний» член.
  2. внутри вашей функции операция A[i][j] не может быть выполнена, так как ваш компилятор не имеет информации о длине строки.

В этом коде есть две основные проблемы.

 MyFunction(int **array, int row, int col); 

Во-первых, это то, что int **array используется неверным типом. Это указатель на указатель, в то время как

 int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 

является многомерным массивом. Память, которая составляет этот multidimensional array, – это всего один кусок, а смещение от начала этого к любому элементу этого массива вычисляется на основе знания размера строки в этом массиве.

 int *A[99]; 

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

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

 int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; printf("%p %p %p\n", array, array[0], &(array[0][0]) ); 

Вы должны получить тот же адрес, напечатанный 3 раза, потому что все они относятся к одному и тому же адресу, но их типы не совпадают. Тип данных последних двух аналогичен и совместим для многих целей, так как array[0] будет рассматриваться как указатель на первый элемент первой строки array и эта строка сама по себе является массивом.

Если вы скажете:

 int **A; 

Вы говорите, что есть указатель на указатель на int . Хотя A[2][4] является допустимым выражением, это не multidimensional array так же, как:

 int B[3][3]; 

Если вы скажете, что A[1] оценивает значение int * аналогичное B[1] , за исключением того, что вы можете сказать A[1] = (int *)0x4444; , но если вы сказали B[1] = (int *)0x4444; вы получите ошибку компилятора, потому что B[1] на самом деле является вычисленным значением, а не переменной. С B нет массива переменных int * – только некоторые вычисления, основанные на размере строки и адресе самого первого элемента массива.

Этот код должен делать что-то похожее на то, что вы хотели (некоторые изменения форматирования вывода для удобства чтения). Обратите внимание, как изменяется значение индекса в инструкции печати.

 MyFunction(int *array, int row, int col) { int x = 0; for(int i=0 ; i