Путаница с указателями и многомерными массивами - PullRequest
5 голосов
/ 12 октября 2010

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

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);
}

Ответы [ 7 ]

13 голосов
/ 19 октября 2010

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

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

С учетом декларации

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);
  ...
}

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

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

Может помочь следующая таблица - она ​​показывает типы различных выражений массива и то, к чему они переходят, основываясь на их объявлениях (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

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

5 голосов
/ 12 октября 2010

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

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

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

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

Я надеюсь, вы видите разницу сейчас. Когда вы передаете int **, это фактически указатель на массив int * s и NOT указатель на двумерный массив.

Пример для реального 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 * с, и передача его в качестве аргумента приведет к int **.


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

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

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

char (*p)[SIZE] = arr;
// use p like arr, eg. p[1][2]
1 голос
/ 18 октября 2010

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

0 голосов
/ 18 октября 2010

С этим кодом связаны две основные проблемы.

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);
}
0 голосов
/ 18 октября 2010

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

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

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

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

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-массива, чтобы компилятор мог выполнить эту арифметику.

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

0 голосов
/ 12 октября 2010

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

Кроме того, здесь есть полезная информация: http://c -faq.com / aryptr / index.html

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