Путаница в отношении указателей и многомерных массивов
Если возможно следующее:
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);
}
Ответы
Ответ 1
Во-первых, некоторый стандартный язык:
6.3.2.1 Lvalues, массивы и обозначения функций
...
3 За исключением случаев, когда это операнд оператора sizeof или унарный оператор & или строковый литерал, используемый для инициализации массива, выражение, которое имеет тип "массив типа", преобразуется в выражение с типом "указатель на тип", который указывает на начальный элемент объекта массива и не является значением lvalue. Если объект массива имеет класс хранения регистров, поведение undefined.
Учитывая объявление
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
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);
...
}
Следуя приведенному выше правилу, когда в инициализаторе для a
появляются выражения d0
, d1
и d2
, их типы все преобразуются из "3-элементного массива int
" в "указатель на int
". Аналогично, когда выражение a
появляется при вызове AnotherFunc
, его тип преобразуется из "3-элементного массива указателя в int
" в "указатель на указатель на int
".
Обратите внимание, что в AnotherFunc
мы индексируем обе измерения вместо вычисления смещения, как это было в MyFunc
. Это потому, что a
- это массив значений указателя. Выражение arr[i]
получает значение i-го значения указателя из местоположения arr
; мы затем находим j'th целочисленное значение, смещенное от этого значения указателя.
Следующая таблица может помочь - она отображает типы различных выражений массива и их распад на основе их объявлений (T (*)[N]
- тип указателя, а не тип массива, поэтому он не распадается):
Declaration Expression Type Implicitly Converted (Decays) to
----------- ---------- ---- --------------------------------
T a[N] a T [N] T *
&a T (*)[N]
*a T
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
Образец для многомерных массивов должен быть ясным.
Ответ 2
Изменить: Здесь моя попытка ответить на более точный ответ в соответствии с запросом и на основе вашего нового примера кода:
Независимо от размеров массива, то, что вы передаете, является "указателем на массив" - это только один указатель, хотя тип указателя может меняться.
В первом примере 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 *
и 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]
Ответ 3
Другие довольно много подвели итоги.
int ** A означает, что A является указателем на массив, а не ссылкой на двухмерный массив.
Однако это не означает, что он неприменим. Поскольку данные в C хранятся в строчном порядке, как только вы знаете длину строки, извлечение данных должно быть легким.
Ответ 4
Поскольку указатель указателя не является тем же типом, что и указатель на массив. Подробнее см. указатели на указатели и указательные массивы.
Кроме того, у этого есть хорошая информация: http://c-faq.com/aryptr/index.html
Ответ 5
Первый пример возможен, потому что массивы вырождаются в указатели при передаче в качестве параметров функции.
Второй пример не работает, потому что int[3][3]
вырождается до int (*)[3]
, а не двойной указатель int **
. Это так, потому что 2D-массивы смежны в памяти, и без этой информации компилятор не знал, как получить доступ к элементам, находящимся за первой строкой. Рассмотрим простую сетку чисел:
1 2 6
0 7 9
Если бы мы сохраняли эти числа в массиве int nums[6]
, как бы мы индексировали в массив для доступа к элементу 7? По 1 * 3 + 1
, конечно, или, вообще говоря, row * num-columns + column
. Чтобы получить доступ к любому элементу за первой строкой, вам нужно знать, сколько столбцов имеет сетка.
Когда вы сохраняете числа как nums[2][3]
, компилятор использует точно такую же арифметику row * num-columns + column
, как и вручную, с массивом 1D, он просто скрыт от программиста. Поэтому вам необходимо передать количество столбцов при передаче 2D-массива, чтобы компилятор мог выполнить эту арифметику.
Во многих других языках массивы несут информацию об их размере, исключая необходимость вручную указывать размеры при передаче многомерных массивов в функции.
Ответ 6
Возможно, мы сможем ожидать более "точного" вопроса, если вы хотите получить более точный ответ. У вашей идеи две проблемы:
- 2D-массив
int A[3][3]
при использовании
в выражении распадается на
адрес его первого элемента таким образом, чтобы
указатель типа int (*)[3]
. Чтобы передать массив, вы должны использовать &A[0][0]
, чтобы получить указатель на первый "внутренний" элемент.
- внутри вашей функции операция
A[i][j]
не может быть выполнено с тех пор
ваш компилятор не имеет информации о
длина строки, там.
Ответ 7
В этом коде есть две основные проблемы.
MyFunction(int **array, int row, int col);
Во-первых, используется int **array
неправильный тип. Это указатель на указатель, а
int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
- многомерный массив. Память, которая составляет этот многомерный массив, - это один кусок, а смещение от начала этого к любому элементу этого массива вычисляется на основе знания размера строки в этом массиве.
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]
является допустимым выражением, это не многомерный массив таким же образом, как:
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<row ; i++ )
{
for(int j=0 ; j<col ; j++)
{
printf("%d ", array[x++]);
}
printf("\n");
}
}
main()
{
int array[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
MyFunction(array, 3, 3);
}