Имя двумерного массива в C - PullRequest
       0

Имя двумерного массива в C

0 голосов
/ 25 февраля 2019

Я изучаю указатели и массивы на C, и вот вопрос, который меня некоторое время смущал:

Так что имя массива 1D int - это постоянный указатель на int ,который указывает на первый элемент в этом массиве.Поэтому, когда мы оцениваем имя одномерного массива, мы должны получить адрес первого элемента в массиве.

Для массива 2D int имя массива является указателем на первый массив int .Так, каково будет значение имени двумерного массива int?Я думаю, что это должен быть адрес первого массива в этом 2D массиве.Но как адрес массива определен в C?Это просто адрес первого элемента в этом массиве?

Ответы [ 6 ]

0 голосов
/ 25 февраля 2019

Массив не является указателем.Имя массива, когда оно используется в выражении, «разлагается» на указатель на первый элемент.

Строго говоря, C имеет только одномерные массивы, а 2D-массив на самом деле является просто массивом массивов.

1D массив:

  • Первый элемент int arr [x], это int.
  • Когда в выражении используется arr, вы получаете указатель на этот элемент, int*.
  • При выполнении арифметики с указателем для этого указателя каждый элемент имеет размер первогоelement = sizeof(arr[0]).

2D массив:

  • Первый элемент int arr [x][y] - это int [y].
  • Когда в выражении используется arr, вы получаете указатель на этот элемент, int (*)[y].
  • При выполнении арифметики указателя для этого указателя каждый элемент имеет размер первого элемента = sizeof(arr[0]).

Так что это то же правило.int(*)[y] указатель массива следует тем же правилам арифметики указателя, что и обычный указатель.Но вы можете прекратить ссылаться на него еще на один шаг, чтобы получить отдельный int в массиве массивов.

0 голосов
/ 25 февраля 2019

«Итак, каково будет значение имени массива 2D int?»

«Я действительно понимаю, что массив не является указателем. В моем вопросе я имею в виду, что когдаимя массива используется в выражении, компилятор сгенерирует указатель константы. "

Вы должны быть осторожны здесь.Как продолжение вашего комментария под вашим вопросом, есть нюансы в том, как применяются правила преобразования массивов / указателей, которые влияют на type, который получается в результате преобразования.Это будет определять, можно ли и как использовать имя массива в выражении.

"... компилятор сгенерирует указатель константы."

Компилятор не генерирует указатель-константу, компилятор следует C11 Стандарт - 6.3.2.1 Другие операнды - L-значения, массивы и указатели функций (p3) .Когда имя массива используется в выражении, выражение оценивается по адресу, который является результатом преобразования массива в указатель (с учетом 4-исключений, указанных в пункте 3).

Правило, касающеесяПреобразование массив / указатель не зависит от количества измерений, правило применяется одинаково независимо. Однако , type указателя, который получается в результате преобразования , действительно зависит от количества измерений массива.Это очень важно, и это будет диктовать, действительно ли вы используете имя массива.

Один из способов помочь закрепить то, что происходит в процессе конверсии, - сделать это шаг за шагом.Начните с 1D массива и продолжайте свой путь вверх.

6.3.2.1 - Преобразование 1D массива в указатель при доступе

Если у вас есть простой массив, например

int array[10];

При доступе к массиву преобразуется в указатель на первый элемент, например, адрес элемента , &array[0].(это просто указатель на int или с формальным типом int *)

6.3.2.1 - Преобразование двухмерного массива в указатель при доступе

Для двумерного массива правило применяется точно так же, например,

int array[10][10];

Здесь array, двумерный массив, по сути, представляет собой массив из 10 - int[10] массивов (массив 1Dмассивы).При доступе array[10][10] преобразуется в указатель на первый массив 10-int точно таким же образом, &array[0][0] (что приводит к указателю на массив int[10] или с формальным типомint (*)[10]) Это не указатель на указатель (например, int**), это, в частности, указатель на массив int[10].

( note важное различие между int *[10] (массив из 10 указателей , который при обращении станет указателем на указатель)) и int (*)[10] (указатель на массив из 10 int))

Ответ

"Итак ... значение имени двумерного массива int при использовании в выражении" - это адрес первого 1D массива целых чисел, которые составляют двумерный массив формального типаint (*)[N] (где N - количество элементов в строке).

Нюанс в применении стандарта

Тип является критическим для правильногоиспользование имени массива.Для двумерного массива результирующий адрес является указателем на массив .Что приведет к разыменованию этого указателя?( Ответ: и массив ) Что происходит, когда вы получаете доступ к этому массиву через разыменованный указатель?(подсказка: преобразование в правилах доступа применяется снова).Вы должны знать, какой будет тип указателя, полученный в результате преобразования, чтобы правильно использовать имя массива в выражении.

Пример может помочь

Или нет, но работа с типами указателей, полученными в результате доступа к массиву и преобразования указателей, может помочь в этом разобраться. Ниже пример объявляет простой 4x3 2D-массив int.Затем он объявляет указатель (p) надлежащего типа, чтобы разрешить использование имени массива в выражении, присваивающем адрес массива указателю.Указатель, инициализированный именем массива, затем используется для дальнейшей инициализации целочисленного указателя (ip) на первый элемент в первом массиве.

В этом примере выводится адрес для каждого элемента, а затем с помощьюуказатель p выводит адрес начала каждого массива строк, который составляет двумерный массив.Наконец, код входит в цикл проверки, сравнивая адреса каждого элемента по (1) индексу массива, (2) адресу, удерживаемому указателем p с использованием смещения, и (3) адресу, удерживаемому ip.Целью является использование каждого из различных указателей, полученных в результате выражения, присваивающего имя массива для ссылки на каждый элемент и обеспечивающего совпадение адресов, содержащихся в каждом указателе.

#include <stdio.h>

int main (void) {

    int array[ ][3] = { {1, 2, 3},      /* 2D array values */
                        {3, 4, 5},
                        {5, 6, 7},
                        {7, 8, 9} },
        (*p)[3] = array,                /* pointer to array */
        *ip = *p;                       /* integer poiner */
    size_t  size = sizeof array,
            nele = size / sizeof **array,
            nrow = size / sizeof *array,
            ncol = sizeof *array / sizeof **array;

    printf ("2D array statistics:\n\n"
            "  size: %zu  (bytes)\n  nele: %zu  (ints)\n"
            "  nrow: %zu\n  ncol: %zu\n",
            size, nele, nrow, ncol);

    puts ("\naddress of each array element:\n");
    for (size_t i = 0; i < nrow; i++) {
        for (size_t j = 0; j < ncol; j++)
            printf ("  %p", (void*)&array[i][j]);
        putchar ('\n');
    }

    puts ("\naddress of each 1D array:\n");
    for (size_t i = 0; i < nrow; i++)
        printf ("  %p\n", (void*)p[i]);

    puts ("\nvalidating each array element address by index & pointer:\n");
    for (size_t i = 0; i < nrow; i++) {
        for (size_t j = 0; j < ncol; j++) {
            if (ip != &array[i][j] || ip != *p + j) {
                fprintf (stderr, "address validation failed for "
                        "array[%zu][%zu]\n(%p != %p || %p != %p)\n",
                        i, j, (void*)ip, (void*)&array[i][j],
                        (void*)ip, (void*)(p + j));
                return 1;
            }
            ip++;
        }
        p++;
    }
    puts ("  done!");

    return 0;
}

Пример использования / Вывод

$ ./bin/array_2d_access
2D array statistics:

  size: 48  (bytes)
  nele: 12  (ints)
  nrow: 4
  ncol: 3

address of each array element:

  0x7ffe7c9a9780  0x7ffe7c9a9784  0x7ffe7c9a9788
  0x7ffe7c9a978c  0x7ffe7c9a9790  0x7ffe7c9a9794
  0x7ffe7c9a9798  0x7ffe7c9a979c  0x7ffe7c9a97a0
  0x7ffe7c9a97a4  0x7ffe7c9a97a8  0x7ffe7c9a97ac

address of each 1D array:

  0x7ffe7c9a9780
  0x7ffe7c9a978c
  0x7ffe7c9a9798
  0x7ffe7c9a97a4

validating each array element address by index & pointer:

  done!

Дайте мне знать, помогло ли это и есть ли у вас какие-либо дополнительные вопросы.

0 голосов
/ 25 февраля 2019

Давайте разберемся с некоторыми вещами:

int a = 24;

Выше много вещей:

  • объявление: мы объявляем переменную с именем a типа int.
  • определение: объект типа int создан.
  • инициализация: этот объект инициализируется значением 24

Итак, подведем итоги: объект типа int создается со значением 24 и переменной a names it.

Теперь давайте применим то же самое к следующему:

int a1[3] = {0, 1, 2};
  • объявление: мы объявляем переменную с именем a1 типа int[3] (он же массив из 3 целых чисел).
  • определение: объект типа "массив из 3 целых чисел"создается
  • инициализация: объект инициализируется с {0, 1, 2}

Переменная a1 называет этот объект.

Таким образом, имямассива 1D int - это постоянный указатель на int, который указывает на первый элемент в этом массиве.

Неверно.Я знаю, что вам, возможно, сказали или прочитали это, но это неправильно.Массив не указатель !!Массивы и указатели бывают разных типов.При этом для удобства и исторических причин в большинстве ситуаций (но не во всех!) Массив распадается на указатель на первый элемент:

int a1[3] = {0, 1, 2};
int* p = a1; // here a1 decays to a pointer to its first element

В приведенном фрагменте p указывает на элемент0 массива a1

Вы можете просматривать 2D или 3D или nD массив одинаково:

T a2[3] = {l0, l1, l2};

Допустим, T это тип.Выше приведен «массив из 3 T с».

  • , если T равен int, то мы имеем int a2[3] = {0, 1, 2} - массив из 3 целых чисел.Мы называем это массивом 1D int.

  • , но если T равен int[2], то выше становится int a2[3][2] = {{00, 01}, {10, 11}, {20, 21}} - вы можете видеть его как "массив из 3 Ts "или" массив из 3 int[2] "или" массив из 3 массивов из 2 целых чисел ".

И мы можем применить то же правило затухания:

int a2[3][2] = {{00, 01}, {10, 11}, {20, 21}};

int (*p2)[2] = a2; // a2 decays to a pointer to its first element.
                   // Its first element is an array of 2 int.
                   // So a2 decays to `int (*)[2]` -  a pointer to an array of two elements.

В приведенном выше a2 указывает на элемент {00, 01} массива.

0 голосов
/ 25 февраля 2019

Таким образом, имя массива 1D int является постоянным указателем на int

Это неверно, и его часто преподают плохо.Массив - это массив.Вот некоторый код для аналогии:

int x = 5;
double d = x + 1.2;

Во второй строке x - , преобразованный в double для целей добавления.Это не меняет x, который все еще является int, результат преобразования является «временным» и существует только до завершения добавления.Преобразование требует условий оператора +, что оба арифметических операнда должны быть приведены к общему типу (double в этом случае).

В случае массива, скажем, у нас есть char *p = arrayname + 1тогда arrayname все еще является массивом.Но он преобразуется во временный указатель, чтобы можно было добавить (для этого требуется оператор +, он может добавить указатель и целое число).Временный указатель указывает на первый элемент массива, но неверно говорить, что временный указатель является массивом.

Большинство операторов вызывают это преобразование массива во временныйуказатель, но некоторые нет.Поэтому неверно говорить, что arrayname является указателем, поскольку он может использоваться с оператором, который не преобразует массив в указатель, например, sizeof arrayname.

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

Но как адрес массива определен в C?Это просто адрес первого элемента в этом массиве?

Нет.У каждой переменной есть адрес, это относится к массивам и не массивам.Если вы понимаете адрес int, то вы также понимаете адрес массива 2x2 char.

0 голосов
/ 25 февраля 2019

Имя массива не является указателем.В большинстве случаев, когда используется имя массива, он неявно * конвертируется * в указатель на свой первый элемент, говорят, что массив превращается в указатель.

Имя массиване затухает до указателя, когда он является аргументом оператора адреса (&), оператора sizeof и когда строковый литерал (который является массивом некоторого символьного типа) используется для инициализации массива *).

Тем не менее, двумерные массивы

T arr[COLS][ROWS];

первый элемент - это массив типа T[ROWS].Таким образом, arr распадается на указатель типа T(*)[ROWS], который указывает на первый элемент arr.


*) Если вы хотите добавить, что массивы также не распадаются, когда они являются операндами _Alignof -оператора, или читайте это где-то еще:

@ EricPostpischi: Массивы не могут быть операндами _Alignof.Включение _Alignof в исключения для преобразования массива было ошибкой в ​​стандарте C 2011._Alignof Операнды могут быть только типами, а не выражениями.

0 голосов
/ 25 февраля 2019

Когда 2D-массив распадается на указатель, у вас есть указатель на массив.Вот пример того, как это будет выглядеть:

int arr[5][6];
int (*p)[6] = arr;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...