Поведение 2D-массивов

Я создал 2D-массив и попытался напечатать определенные значения, как показано ниже:

int a[2][2] = { {1, 2}, 
                {3, 4}};

printf("%d %d\n", *(a+1)[0], ((int *)a+1)[0]);

Вывод:

3 2

Я понимаю, почему 3 - это первый вывод (a+1 указывает на вторую строку, и мы печатаем его элемент 0th.

Мой вопрос касается второго выхода, т.е. 2. Я предполагаю, что из-за typecasting a как int *, 2D-массив обрабатывается как 1D-массив, и поэтому a+1 действует как указатель на элемент 2nd, и поэтому мы получаем вывод как 2.

Правильны ли мои предположения или есть ли какая-то другая логика? Кроме того, изначально какой тип a обрабатывается как указатель int (*)[2] или int **?

Ответы

Ответ 1

Правильны ли мои предположения или есть ли другая логика?

Да.

*(a+1)[0] эквивалентен a[1][0].
((int *)a+1)[0] эквивалентно a[0][1].

Объяснение:

a распадается на указатель на первый элемент 2D-массива, то есть на первую строку. *a разделяет эту строку, которая представляет собой массив из 2 int. Поэтому *a можно рассматривать как имя массива первой строки, которое далее распадается на указатель на его первый элемент, т.е. 1. *a + 1 даст указатель на второй элемент. Выделение *a + 1 даст 1. Итак:

((int *)a+1)[0] == *( ((int *)a+1 )+ 0) 
                == *( ((int *)a + 0) + 1) 
                == a[0][1]   

Обратите внимание, что a, *a, &a, &a[0] и &a[0][0] все имеют одинаковое значение адреса, хотя они имеют разные типы. После распада a имеет тип int (*)[2]. Отбрасывая его на int *, просто вводится значение адреса int *, а арифметика (int *)a+1 указывает адрес второго элемента.

Также, изначально какой тип a обрабатывается как указатель (int (*)[2] или int **?

Он становится указателем типа на массив из 2 int, i.e int (*)[2]

Ответ 2

Когда вы написали выражение

(int *)a

то логически исходный массив можно рассматривать как одномерный массив следующим образом

int a[4] = { 1, 2, 3, 4 };

Таким образом, выражение a указывает на первый элемент, равный 1 этого мнимого массива. Выражение ( a + 1 ) указывает на второй элемент мнимого массива, равный 2, а выражение ( a + 1 )[0] возвращает ссылку на этот элемент, который вы получаете 2.

Ответ 3

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

Когда вы отбрасываете a в int*, вы удаляете это знание, и оно обрабатывается как обычный одномерный массив (который в вашем случае выглядит в памяти, например 1 2 3 4).

Ответ 4

Ключевое значение для распознавания здесь - это то, что a содержит значение адреса, в котором находится первая строка. Поскольку весь массив начинается с того же места, что и весь массив, он имеет одинаковое значение адреса; то же самое для самого первого элемента.

В C термины:

&a == &a[0];
&a == &a[0][0];
&a[0] == &a[0][0];
// all of these hold true, evaluate into 1
// cast them if you want, looks ugly, but whatever...
&a == (int (*)[2][2]) &a[0];
&a == (int (*)[2][2]) &a[0][0];
&a[0] == (int (*)[2]) &a[0][0];

По этой причине, когда вы отбрасываете a в int *, он просто становится равным 1 к 1 эквиваленту &a[0][0] как с помощью типа, так и значения. Если вы должны применить эти операции к &a[0][0]:

(&a[0][0] + 1)[0];
(a[0] + 1)[0];
*(a[0] + 1);
a[0][1];

Что касается типа a, если он рассматривается как указатель, хотя я не уверен, должен быть int (*)[2].